VuReact 是一个能将 Vue 3 代码编译为标准、可维护 React 代码的工具。今天就带大家直击核心:Vue 中常见的 defineModel 宏经过 VuReact 编译后会变成什么样的 React 代码?
前置约定
为避免示例代码冗余导致理解偏差,先明确几个小约定:
- 文中 Vue / React 代码均为核心逻辑简写,省略完整组件包裹、无关配置等内容;
- 默认读者已熟悉 Vue 3 中
defineModel的 API 用法与核心行为; - 仅支持
type、default、required选项及自定义 prop name; - 不支持返回值使用数组解构方式。
编译对照
defineModel 基础用法
defineModel 是 Vue 3 <script setup> 中用于简化 v-model 双向绑定声明的宏。Vue 中 defineModel 会在编译时自动创建一个 ref 并生成对应的 modelValue prop 和 update:modelValue 事件。VuReact 会将它编译为 useVRef 将 prop 值转为响应式 ref,同时配合 useUpdated 实现值变化时自动触发 onUpdate:xxx 回调通知父组件。
- Vue 代码:
ts
<script setup lang="ts">
// 声明 "state" prop,由父组件通过 v-model:state 使用
const state = defineModel<string>('state');
// 声明带选项的 "modelValue" prop,由父组件通过 v-model 使用
const modelValue = defineModel({ default: 'xxx' });
</script>
- VuReact 编译后 React 代码:
ts
import { useVRef, useUpdated } from '@vureact/runtime-core';
type IChildProps = {
state?: string;
modelValue?: string;
} & {
onUpdateState?: (arg: string) => void;
onUpdateModelValue?: (arg: string) => void;
};
// 声明 "state" prop,由父组件通过 v-model:state 使用
const state = useVRef<string>(props.state);
// 声明带选项的 "modelValue" prop,由父组件通过 v-model 使用
const modelValue = useVRef<string>(props.modelValue ?? 'xxx');
// 值变化时自动通知父组件
useUpdated(() => {
props.onUpdateState?.(state.value);
}, [state.value]);
useUpdated(() => {
props.onUpdateModelValue?.(modelValue.value);
}, [modelValue.value]);
从示例可以看到:Vue 的 defineModel 被分解为三部分:
- prop 类型声明 →
IChildProps类型中的非事件字段(state?、modelValue?) - 事件回调声明 →
IChildProps类型中的onUpdateXxx事件回调字段 - 运行时响应式 →
useVRef将 prop 初始值转为响应式 ref,useUpdated监听值变化并自动调用父组件传入的回调
核心行为 :VuReact 保证组件内直接修改 defineModel ref 的 .value 就能触发父组件更新,与 Vue 开发体验完全一致。
defineModel 带选项
defineModel 支持传入 name 指定 prop 名称,以及 type、default、required 选项。VuReact 会将这些选项转换为对应的 React 类型约束和默认值处理。
- Vue 代码:
ts
<script setup lang="ts">
// 声明带选项的 "count" prop,由父组件通过 v-model:count 使用
const count = defineModel<number>('count', {
type: Number,
default: 0,
required: true,
});
</script>
- VuReact 编译后 React 代码:
ts
type IChildProps = {
count: number; // required: true → 非可选类型
} & {
onUpdateCount?: (arg: number) => void;
};
const count = useVRef<number>(props.count ?? 0); // default: 0 → 默认值回退
字段选项处理规则:
- required: true :使
count在类型定义中变为必填(count: number),而非可选(count?: number) - default: 0 :通过
??空值合并运算符实现,在父组件未传递 prop 时使用默认值0 - type: Number :影响类型定义中的泛型参数(
<number>),VuReact 会利用此信息生成准确的 TypeScript 类型
modelValue 属性覆盖赋值
在 Vue 中,defineModel 返回的是一个 ref 对象,需要通过 .value 访问和修改状态;在 React 中,VuReact 编译后依然保留 .value 的访问方式。
- Vue 代码:
ts
<script setup lang="ts">
const state = defineModel<string>('state');
const update = () => {
state.value = 'hello'; // 直接赋值修改
};
</script>
- VuReact 编译后 React 代码:
ts
const state = useVRef<string>(props.state);
const update = useCallback(() => {
state.value = 'hello'; // 直接赋值修改,自动触发 props.onUpdateState 回调
}, [state.value]);
编译规则:
- useCallback 包裹 :为涉及
state.value的函数自动包裹useCallback - 正确依赖收集 :在依赖数组中正确添加
state.value - 触发父组件更新 :
state.value = 'hello'可同时完成组件内状态更新和父组件的双向绑定同步
v-model 模板绑定
在 Vue 模板中,可以直接使用 v-model 绑定由 defineModel 声明的 ref;在 React 中,VuReact 会编译为受控组件的 value + onChange 模式。
- Vue 代码:
html
<template>
<input v-model="modelValue" />
<div>Parent bound v-model is: {{ count }}</div>
<button @click="update">Increment</button>
</template>
- VuReact 编译后 React 代码:
ts
<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 转换规则:
- value 绑定 :ref 的值绑定到
value属性 - onChange 处理 :在
onChange中直接修改 ref 值 - 自动同步 :值修改后触发
useUpdated自动同步给父组件
不支持的 defineModel 用法
VuReact 明确不支持以下 defineModel 用法,编译时会跳过和报错。
1. 返回值数组解构
html
<script setup lang="ts">
// 不支持的写法(Vue 3.4+ 实验特性)
const [arg1, arg2] = defineModel();
</script>
Vue 3.4+ 支持将 defineModel 返回值解构为 [model, modifiers] 元组,用于获取修饰符状态。VuReact 暂不支持此语法,建议使用标准写法:
html
<script setup lang="ts">
// 支持的写法
const model = defineModel();
</script>
2. get / set / validator 选项
html
<script setup lang="ts">
// 不支持的写法
const modelValue = defineModel({
get() {},
set() {},
validator() {},
});
</script>
Vue 中 defineModel 支持 get、set 自定义存取器和 validator 验证函数。VuReact 暂不支持这些选项,建议直接使用 useVRef 自行实现自定义逻辑。
编译策略总结
VuReact 的 defineModel 编译策略展示了完整的双向绑定转换能力:
- Vue 宏分解 :将
defineModel分解为类型声明、事件回调、运行时响应式三部分 - 选项支持 :支持
type、default、required等选项的转换 - 赋值习惯保留 :保持
.value的访问和修改方式,与 Vue 开发体验一致 - 模板 v-model:编译为 React 标准的受控组件模式
核心功能:
- 自动响应式 :通过
useVRef将 prop 转为响应式 ref - 自动通知 :通过
useUpdated监听值变化并自动触发父组件回调 - 类型安全:根据选项自动推导 TypeScript 类型
- 函数缓存 :使用
useCallback优化涉及 ref 操作的函数
注意事项:
- 仅支持 prop name :
defineModel('xxx')形式传递 prop 名称 - 仅部分选项 :仅支持
type、default、required三个选项 - 不支持解构 :暂不支持
[model, modifiers]数组解构语法 - 不支持存取器 :暂不支持
get、set、validator选项
VuReact 的编译策略确保了从 Vue 到 React 的平滑迁移,开发者无需手动实现双向绑定逻辑。编译后的代码既保持了 Vue 的 v-model 语义和 .value 赋值习惯,又符合 React 的受控组件设计模式,让迁移后的应用保持完整的双向绑定能力。
综合示例对照
完整的 defineModel 单文件组件编译前后对照,可参考以下代码:
- Vue 代码(
input.vue):
html
<script setup lang="ts">
// @vr-name: Child
// 声明 "state" prop,由父组件通过 v-model:state 使用
const state = defineModel<string>('state');
// 声明带选项的 "modelValue" prop,由父组件通过 v-model 使用
const modelValue = defineModel({ default: 'xxx' });
// 声明带选项的 "count" prop,由父组件通过 v-model:count 使用
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>
- VuReact 编译后 React 代码(
output.tsx):
ts
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;
🔗 相关资源
- VuReact 官方文档:语义编译对照总览
- VuReact Runtime:useVRef / useUpdated
- Github:https://github.com/vureact-js/core
📖 继续阅读
- 上一篇:toRaw
✨ 如果你觉得本文对你理解 VuReact 有帮助,欢迎点赞、收藏、关注!