VuReact 是专为 Vue 迁移 React 设计的智能编译器。它用于将 Vue 3 单文件组件・脚本・样式完整转为纯 React(非运行时桥接)代码并输出工程化产物,覆盖 <script setup> 核心全特性,支持渐进式迁移与 Vue+React 混合开发。
今天就带大家直击核心:Vue 中常见的 withDefaults 编译宏经过 VuReact 编译后会变成什么样的 React 代码?
前置约定
为避免示例代码冗余导致理解偏差,先明确几个小约定:
- 文中 Vue / React 代码均为核心逻辑简写,省略完整组件包裹、无关配置等内容;
- 默认读者已熟悉 Vue 3 中
withDefaults的 API 用法与核心行为; withDefaults()必须赋值给一个变量;- 第一个参数必须是
defineProps()调用,第二个参数必须是内联对象字面量。
编译对照
withDefaults 基础用法
withDefaults 是 Vue 3 <script setup> 中用于为 defineProps 声明的 prop 提供编译时默认值的工具函数。Vue 中 withDefaults 会在编译时生成默认值逻辑,确保父组件未传递的 prop 拥有默认值。VuReact 会将它编译为 useMemo,在组件初始化时合并传入的 props 与默认值,生成一个包含完整默认值的只读 props 对象。
- Vue 代码:
ts
<script setup lang="ts">
interface Props {
msg?: string;
count?: number;
labels: string[];
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
count: 42,
labels: () => ['one', 'two'],
});
</script>
- VuReact 编译后 React 代码:
ts
import { useMemo, memo } from 'react';
interface Props {
msg?: string;
count?: number;
labels: string[];
}
export type ICompProps = Props;
const Input = memo((vrProps: ICompProps) => {
/* from withDefaults */
const props = useMemo<Readonly<Props>>(() => ({
...vrProps,
msg: vrProps.msg ?? 'hello',
count: vrProps.count ?? 42,
labels: vrProps.labels ?? ['one', 'two'],
}), [vrProps]);
});
从示例可以看到:Vue 的 withDefaults 被编译为 React 的 useMemo + 空值合并运算符 ?? 组合。主要分为三部分:
- 类型保留 →
Props接口原样保留,不会因默认值而改变类型的可选/必填约束 。msg?和count?仍是可选类型; - 默认值合并 →
useMemo中使用展开运算符...vrProps保留所有传入值,再对每个具有默认值的字段通过??空值合并运算符进行回退填充; - 只读保证 →
useMemo<Readonly<Props>>确保返回的 props 对象是只读的,与 Vue 中withDefaults的运行时不可变性一致。
核心行为 :VuReact 保证组件内通过 props.xxx 访问的始终是包含默认值的完整属性集,与 Vue 开发体验完全一致。
基本类型默认值
对于 string、number 等基本类型的默认值,VuReact 直接使用 ?? 空值合并运算符:
- Vue 代码:
ts
<script setup lang="ts">
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
count: 42,
});
</script>
- VuReact 编译后 React 代码:
ts
const props = useMemo(() => ({
...vrProps,
msg: vrProps.msg ?? 'hello',
count: vrProps.count ?? 42,
}), [vrProps]);
字段选项处理规则:
- default: 'hello' :通过
??空值合并运算符实现,仅在父组件未传递该 prop(即undefined)时生效 - default: 42 :基本类型默认值直接作为
??右侧的字面量值 - 类型保留 :
Props中msg?和count?的可选性不受默认值影响
引用类型默认值
对于数组、对象等引用类型,Vue 的 withDefaults 要求使用工厂函数(如 () => ['one', 'two'])以避免多个实例共享同一引用。VuReact 同样遵循这一约定,直接在 ?? 右侧调用工厂函数,保证每次渲染生成独立的引用实例:
- Vue 代码:
ts
<script setup lang="ts">
interface Props {
labels: string[];
}
const props = withDefaults(defineProps<Props>(), {
labels: () => ['one', 'two'],
});
</script>
- VuReact 编译后 React 代码:
ts
const props = useMemo<Readonly<Props>>(() => ({
...vrProps,
labels: vrProps.labels ?? ['one', 'two'],
}), [vrProps]);
VuReact 保证 ?? 右侧的工厂函数每次执行都会返回新实例,避免引用共享导致的副作用污染。
编译规则:
- 工厂函数调用:引用类型默认值作为工厂函数调用,确保独立性
- 数组默认值 :
['one', 'two']在每次渲染时创建新数组 - 对象默认值:对象字面量同样遵循此模式,防止跨组件实例的引用共享
不支持的 withDefaults 用法
VuReact 明确不支持以下 withDefaults 用法,编译时会报错提示。
1. 未赋值给变量
withDefaults() 必须赋值给一个变量(如 const props = withDefaults(...)),不支持作为独立表达式调用:
html
<script setup lang="ts">
// 不支持的写法
withDefaults(defineProps<Props>(), { msg: 'hello' });
</script>
html
<script setup lang="ts">
// 支持的写法
const props = withDefaults(defineProps<Props>(), { msg: 'hello' });
</script>
2. 第一个参数非 defineProps() 调用
withDefaults() 的第一个参数必须是 defineProps() 的调用表达式,不支持传入其他表达式:
html
<script setup lang="ts">
// 不支持的写法
const props = withDefaults({ msg: 'hello' });
</script>
html
<script setup lang="ts">
// 支持的写法
const props = withDefaults(defineProps<Props>(), { msg: 'hello' });
</script>
3. 第二个参数非对象字面量
withDefaults() 的第二个参数必须是内联的对象字面量,不支持传入变量引用或其他表达式:
html
<script setup lang="ts">
// 不支持的写法
const defaults = { msg: 'hello' };
const props = withDefaults(defineProps<Props>(), defaults);
</script>
html
<script setup lang="ts">
// 支持的写法
const props = withDefaults(defineProps<Props>(), { msg: 'hello' });
</script>
编译策略总结
VuReact 的 withDefaults 编译策略展示了完整的默认值转换能力:
- Vue 宏分解 :将
withDefaults分解为类型保留、默认值合并、只读保证三部分 - 类型安全:Props 接口原样保留,不因默认值改变可选性
- 合并策略 :通过
useMemo合并原始 props 与默认值,确保性能优化 - 引用独立:引用类型默认值使用工厂函数,保证每次渲染独立实例
核心功能:
- 自动合并 :通过
...vrProps+??空值合并实现默认值合并 - 只读保证 :
useMemo<Readonly<Props>>确保 props 不可变 - 类型保留:接口定义原样保留,不影响外部类型约束
- 性能优化 :
useMemo缓存合并结果,减少不必要的计算
注意事项:
- 必须赋值给变量 :
withDefaults()必须配合const props = ...使用 - 仅支持
defineProps():第一个参数必须是defineProps()调用 - 仅支持对象字面量:第二个参数必须是内联对象字面量
- 引用类型用工厂:数组、对象等引用类型必须使用工厂函数形式
VuReact 的编译策略确保了从 Vue 到 React 的平滑迁移,开发者无需手动实现默认值合并逻辑。编译后的代码既保持了 Vue 的 withDefaults 语义和类型推导能力,又符合 React 的 useMemo 性能优化模式,让迁移后的应用保持完整的默认值处理能力。
综合示例对照
完整的 withDefaults 单文件组件编译前后对照,可参考以下代码:
- Vue 代码(
input.vue):
html
<script setup lang="ts">
// @vr-name: CompWithDefaults
interface Props {
msg?: string;
count?: number;
labels: string[];
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
count: 42,
labels: () => ['one', 'two'],
});
</script>
<template>
<div>{{ props.msg }} {{ props.count }}</div>
<ul>
<li v-for="value in props.labels" :key="value">{{ value }}</li>
</ul>
</template>
- VuReact 编译后 React 代码(
output.tsx):
ts
import { useMemo, memo } from 'react';
interface Props {
msg?: string;
count?: number;
labels: string[];
}
export type ICompProps = Props;
const CompWithDefaults = memo((vrProps: ICompProps) => {
/* from withDefaults */
const props = useMemo<Readonly<Props>>(() => ({
...vrProps,
msg: vrProps.msg ?? 'hello',
count: vrProps.count ?? 42,
labels: vrProps.labels ?? ['one', 'two'],
}), [vrProps]);
return (
<>
<div>{props.msg}{props.count}</div>
<ul>
{props.labels.map(value => <li key={value}>{value}</li>)}
</ul>
</>
);
});
export default CompWithDefaults;
🔗 相关资源
- VuReact 官方文档:语义编译对照总览
- Github:github.com/vureact-js/... (欢迎 Star ⭐)
✨ 如果你觉得本文对你理解 VuReact 有帮助,欢迎点赞、收藏、关注!