惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
月光博客
月光博客
博客园_首页
博客园 - 叶小钗
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
InfoQ
量子位
小众软件
小众软件
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
IT之家
IT之家
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
G
Google Developers Blog
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How does VuReact compile Vue 3's defineModel to React?
Ryan John · 2026-06-23 · via DEV Community

VuReact is a compiler toolchain for migrating from Vue to React — and for writing React with Vue 3 syntax.

In this article, we will look at how Vue 3's defineModel macro is compiled into React.

Before We Start

To keep the examples easy to read, this article follows a few 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 the API shape and core behavior of Vue 3's defineModel.
  3. VuReact only supports the type, default, required options and a custom prop name.
  4. Array destructuring of the return value is not supported.

Compilation Mapping

Vue defineModeluseVRef + useUpdated for auto-sync

defineModel is a Vue 3 <script setup> macro that simplifies v-model two-way binding declarations. Under the hood, Vue's compiler generates a ref along with a modelValue prop and an update:modelValue event. VuReact compiles this into useVRef (which turns a prop value into a reactive ref) paired with useUpdated (which triggers the parent's onUpdate:xxx callback when the value changes).

  • Vue
// Declares a "state" prop consumed by the parent via v-model:state
const state = defineModel<string>('state');

// Declares a "modelValue" prop (default: 'xxx') consumed via v-model
const modelValue = defineModel({ default: 'xxx' });

  • Compiled React
import { useVRef, useUpdated } from '@vureact/runtime-core';

type IChildProps = {
  state?: string;
  modelValue?: string;
} & {
  onUpdateState?: (arg: string) => void;
  onUpdateModelValue?: (arg: string) => void;
};

// "state" prop consumed by parent via onUpdateState
const state = useVRef<string>(props.state);

// "modelValue" prop (default: 'xxx') consumed via onUpdateModelValue
const modelValue = useVRef<string>(props.modelValue ?? 'xxx');

// Automatically notifies parent on value change
useUpdated(() => {
  props.onUpdateState?.(state.value);
}, [state.value]);

useUpdated(() => {
  props.onUpdateModelValue?.(modelValue.value);
}, [modelValue.value]);

As the example shows, Vue's defineModel is decomposed into three parts:

  1. Prop type declaration — non-event fields in IChildProps (state?, modelValue?);
  2. Event callback declarationonUpdateXxx fields in IChildProps;
  3. Runtime reactivityuseVRef wraps the initial prop value into a reactive ref, and useUpdated watches the ref and automatically invokes the parent-provided callback.

With VuReact, directly mutating the ref's .value inside the component triggers the parent's update — exactly like Vue's development experience.


defineModel(name, options) → typed prop declaration with default

defineModel accepts a name to specify the prop name and limited options (type, default, required). VuReact converts these options into corresponding TypeScript type constraints and default-value handling.

  • Vue
const count = defineModel<number>('count', {
  type: Number,
  default: 0,
  required: true,
});

  • Compiled React
type IChildProps = {
  count: number;  // required: true → non-optional type
} & {
  onUpdateCount?: (arg: number) => void;
};

const count = useVRef<number>(props.count ?? 0); // default: 0

Option mapping rules:

  1. required: true makes count required (count: number) instead of optional (count?: number).
  2. default: 0 is implemented via the ?? nullish coalescing operator, falling back to 0 when the parent does not pass the prop.
  3. type: Number influences the generic type parameter (<number>) in the generated TypeScript definition.

Direct .value assignment → auto-sync with parent

In Vue, defineModel returns a ref; you read and write its .value. VuReact preserves this .value access pattern in the compiled React code.

  • Vue
const state = defineModel<string>('state');

const update = () => {
  state.value = 'hello'; // direct assignment
};

  • Compiled React
const state = useVRef<string>(props.state);

const update = useCallback(() => {
  state.value = 'hello'; // triggers props.onUpdateState automatically
}, [state.value]);

Compilation rules:

  1. useCallback wrapping — functions that touch state.value are wrapped with useCallback.
  2. Correct dependency trackingstate.value is added to the dependency array.
  3. Parent syncstate.value = 'hello' simultaneously updates the component's local state and pushes the new value to the parent.

v-model in templates → React controlled components

Vue templates use v-model to bind a defineModel ref; VuReact compiles this into the standard React controlled-component pattern (value + onChange).

  • Vue
<template>
  <input v-model="modelValue" />
  <div>Parent bound v-model is: {{ count }}</div>
  <button @click="update">Increment</button>
</template>

  • Compiled React
<input
  value={modelValue}
  onChange={(e) => {
    modelValue = e.target.value;
  }}
/>
<div>Parent bound v-model is:{count.value}</div>
<button onClick={update}>Increment</button>

v-model conversion rules:

  1. value binding — the ref's value is bound to the value attribute.
  2. onChange handler — the ref is reassigned directly in the onChange callback.
  3. Automatic sync — the reassignment triggers useUpdated, which pushes the new value to the parent.

Unsupported defineModel patterns

VuReact explicitly does not support the following defineModel usages and will skip them with an error.

1. Array destructuring of the return value

<script setup lang="ts">
// Not supported (Vue 3.4+ experimental feature)
const [arg1, arg2] = defineModel();
</script>

Vue 3.4+ supports destructuring the return value into [model, modifiers] to access modifier status. VuReact does not support this syntax yet. Use the standard form instead:

<script setup lang="ts">
// Supported form
const model = defineModel();
</script>

2. get / set / validator options

<script setup lang="ts">
// Not supported
const modelValue = defineModel({
  get() {},
  set() {},
  validator() {},
});
</script>

Vue's defineModel supports custom get/set accessors and a validator function. VuReact does not support these options yet. Use useVRef directly to implement custom logic if needed.


Full example

Below is a complete single-file component using defineModel and its compiled React output.

  • Vue (input.vue):
<script setup lang="ts">
// @vr-name: Child

const state = defineModel<string>('state');
const modelValue = defineModel({ default: 'xxx' });
const count = defineModel<number>('count', {
  type: Number,
  default: 0,
  required: true,
});

const update = () => {
  state.value = 'hello';
  count.value++;
};
</script>

<template>
  <input v-model="modelValue" />
  <div>Parent bound v-model is: {{ count }}</div>
  <button @click="update">Increment</button>
</template>

  • Compiled React (output.tsx):
import { useCallback, memo } from 'react';
import { useVRef, useUpdated } from '@vureact/runtime-core';

export type IChildProps = {
  state?: string;
  modelValue?: string;
  count: number;
} & {
  onUpdateState?: (arg: string) => void;
  onUpdateModelValue?: (arg: string) => void;
  onUpdateCount?: (arg: number) => void;
};

const Child = memo((props: IChildProps) => {
  const state = useVRef<string>(props.state);
  const modelValue = useVRef<string>(props.modelValue ?? 'xxx');
  const count = useVRef<number>(props.count ?? 0);

  const update = useCallback(() => {
    state.value = 'hello';
    count.value++;
  }, [state.value, count.value]);

  useUpdated(() => {
    props.onUpdateState?.(state.value);
  }, [state.value]);

  useUpdated(() => {
    props.onUpdateModelValue?.(modelValue.value);
  }, [modelValue.value]);

  useUpdated(() => {
    props.onUpdateCount?.(count.value);
  }, [count.value]);

  return (
    <>
      <input
        value={modelValue}
        onChange={(e) => {
          modelValue = e.target.value;
        }}
      />
      <div>Parent bound v-model is:{count.value}</div>
      <button onClick={update}>Increment</button>
    </>
  );
});

export default Child;

Related Links