DEV Community

Cover image for How does VuReact compile Vue v-model to React?
Ryan John
Ryan John

Posted on • Originally published at vureact.top

How does VuReact compile Vue v-model to React?

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:

  1. All Vue and React snippets focus on core logic only, with full component wrappers and unrelated configuration omitted.
  2. The discussion assumes you are already familiar with Vue 3's v-model directive 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" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<input
  value={keyword.value}
  onChange={(value) => {
    keyword.value = value;
  }}
/>
Enter fullscreen mode Exit fullscreen mode

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:

  1. Semantic consistency: Fully simulates Vue v-model behavior by implementing two-way data binding
  2. Controlled component pattern: Uses React's standard controlled component implementation
  3. Event handling: Automatically handles input events and value updates
  4. 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" />
Enter fullscreen mode Exit fullscreen mode
  • 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;
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Radio button

  • Vue
<input type="radio" value="male" v-model="gender" />
<input type="radio" value="female" v-model="gender" />
Enter fullscreen mode Exit fullscreen mode
  • 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' }}
/>
Enter fullscreen mode Exit fullscreen mode

Select dropdown

  • Vue
<select v-model="selected">
  <option value="a">Option A</option>
  <option value="b">Option B</option>
</select>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<input
  value={message.value}
  onBlur={(e) => {
    message.value = e.target.value;
  }}
/>
Enter fullscreen mode Exit fullscreen mode

.number modifier

  • Vue
<input v-model.number="age" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<input
  value={age.value}
  onChange={(e) => {
    age.value = Number(e.target.value);
  }}
/>
Enter fullscreen mode Exit fullscreen mode

.trim modifier

  • Vue
<input v-model.trim="username" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<input
  value={username.value}
  onChange={(e) => {
    username.value = e.target.value?.trim();
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Combined modifiers

  • Vue
<input v-model.lazy.trim="search" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<input
  value={search.value}
  onBlur={(e) => {
    search.value = e.target.value?.trim();
  }}
/>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
  • 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)} />
  );
}
Enter fullscreen mode Exit fullscreen mode

Named v-model

  • Vue
<UserForm v-model:name="userName" v-model:email="userEmail" />
Enter fullscreen mode Exit fullscreen mode
  • Compiled React
<UserForm
  name={userName.value}
  onUpdateName={(value) => {
    userName.value = value;
  }}
  email={userEmail.value}
  onUpdateEmail={(value) => {
    userEmail.value = value;
  }}
/>
Enter fullscreen mode Exit fullscreen mode

Compilation strategy summary

VuReact's v-model compilation strategy demonstrates a complete two-way binding conversion capability:

  1. Basic form elements: Converts v-model on various input types into corresponding controlled components
  2. Modifier support: Fully supports .lazy, .number, .trim and other modifiers
  3. Component v-model: Supports component-level two-way binding, including multiple v-model bindings and custom modifiers
  4. Event mapping: Intelligently maps Vue events to React events (inputonChange, etc.)
  5. 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.

Related Links

Top comments (0)