VuReact is a compiler for migrating from Vue to React — and for writing React with Vue syntax. In this article, we dive straight into the core: how Vue's common v-model directive is compiled into React code by VuReact.
Before We Start
To keep the examples easy to read, this article follows two simple conventions:
- All Vue and React snippets focus on core logic only, with full component wrappers and unrelated configuration omitted.
- The discussion assumes you are already familiar with Vue 3's
v-modeldirective usage.
Compilation Mapping
v-model: Basic form two-way binding
v-model is Vue's syntactic sugar for implementing two-way data binding on form input elements, combining the functionality of v-bind and v-on.
Text input
- Vue
<input v-model="keyword" />
- Compiled React
<input
value={keyword.value}
onChange={(value) => {
keyword.value = value;
}}
/>
As the example shows, Vue's v-model directive is compiled into React's controlled component pattern. VuReact adopts a controlled component compilation strategy, converting the template directive into a combination of value and onChange. This fully preserves Vue's two-way binding semantics — achieving synchronized updates between data and the view.
The key characteristics of this compilation approach are:
-
Semantic consistency: Fully simulates Vue
v-modelbehavior by implementing two-way data binding - Controlled component pattern: Uses React's standard controlled component implementation
- Event handling: Automatically handles input events and value updates
- Reactive integration: Seamlessly integrates with Vue's reactive system
v-model with different input types
Vue's v-model automatically adapts based on the input element type, and VuReact preserves this intelligent adaptation.
Checkbox
- Vue
<input type="checkbox" v-model="checked" />
<input type="checkbox" value="vue" v-model="frameworks" />
- Compiled React
<input
type="checkbox"
checked={checked.value}
onChecked={(e) => {
checked.value = e.target.checked;
}}
/>
<input
type="checkbox"
value="vue"
checked={frameworks.value}
onChange={(e) => {
frameworks.value = e.target.checked;
}}
/>
Radio button
- Vue
<input type="radio" value="male" v-model="gender" />
<input type="radio" value="female" v-model="gender" />
- Compiled React
<input
type="radio"
value="male"
checked={gender.value === 'male'}
onChange={() => { gender.value = 'male' }}
/>
<input
type="radio"
value="female"
checked={gender.value === 'female'}
onChange={() => { gender.value = 'female' }}
/>
Select dropdown
- Vue
<select v-model="selected">
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>
- Compiled React
<select
value={selected.value}
onChange={(e) => {
selected.value = e.target.value;
}}
>
<option value="a">Option A</option>
<option value="b">Option B</option>
</select>
v-model modifiers
Vue's v-model supports various modifiers for controlling when and how data is updated.
.lazy modifier
- Vue
<input v-model.lazy="message" />
- Compiled React
<input
value={message.value}
onBlur={(e) => {
message.value = e.target.value;
}}
/>
.number modifier
- Vue
<input v-model.number="age" />
- Compiled React
<input
value={age.value}
onChange={(e) => {
age.value = Number(e.target.value);
}}
/>
.trim modifier
- Vue
<input v-model.trim="username" />
- Compiled React
<input
value={username.value}
onChange={(e) => {
username.value = e.target.value?.trim();
}}
/>
Combined modifiers
- Vue
<input v-model.lazy.trim="search" />
- Compiled React
<input
value={search.value}
onBlur={(e) => {
search.value = e.target.value?.trim();
}}
/>
Component v-model
Vue 3 made significant improvements to component v-model, supporting multiple v-model bindings and custom modifiers.
Basic component v-model
- Vue
<!-- Parent component -->
<CustomInput v-model="inputValue" />
<!-- Child component CustomInput.vue -->
<script setup lang="ts">
const props = defineProps(['modelValue']);
const emits = defineEmits(['update:modelValue']);
</script>
<template>
<input :value="props.modelValue" @input="(e) => emits('update:modelValue', e.target.value)" />
</template>
- Compiled React
// Parent component
<CustomInput
modelValue={inputValue.value}
onUpdateModelValue={(value) => {
inputValue.value = value;
}}
/>;
// Child component CustomInput.tsx
type ICustomInputProps = {
modelValue?: any;
onUpdateModelValue?: (...args: any[]) => any;
}
function CustomInput(props: ICustomInputProps) {
return (
<input value={props.modelValue} onChange={(e) => props.onUpdateModelValue?.(e.target.value)} />
);
}
Named v-model
- Vue
<UserForm v-model:name="userName" v-model:email="userEmail" />
- Compiled React
<UserForm
name={userName.value}
onUpdateName={(value) => {
userName.value = value;
}}
email={userEmail.value}
onUpdateEmail={(value) => {
userEmail.value = value;
}}
/>
Compilation strategy summary
VuReact's v-model compilation strategy demonstrates a complete two-way binding conversion capability:
-
Basic form elements: Converts
v-modelon various input types into corresponding controlled components -
Modifier support: Fully supports
.lazy,.number,.trimand other modifiers -
Component v-model: Supports component-level two-way binding, including multiple
v-modelbindings and custom modifiers -
Event mapping: Intelligently maps Vue events to React events (
input→onChange, etc.) - Type safety: Preserves TypeScript type definition integrity
Compilation mapping for different element types:
| Element type | Vue event | React event | Value attribute |
|---|---|---|---|
input[type="text"] |
input |
onChange |
value |
textarea |
input |
onChange |
value |
input[type="checkbox"] |
change |
onChange |
checked |
input[type="radio"] |
change |
onChange |
checked |
select |
change |
onChange |
value |
VuReact's compilation strategy ensures a smooth migration from Vue to React. Developers do not need to manually rewrite form binding logic. The compiled code preserves Vue's semantics and convenience while following React's form handling best practices, keeping the migrated application fully interactive.
Top comments (0)