
Vue 3 和 React 都是组件化框架。只要项目稍微复杂一点,就绕不开组件通信:父组件给子组件传数据,子组件通知父组件,兄弟组件共享状态,深层组件读取全局配置,通用组件开放插槽或渲染入口。
表面上看,Vue 有 props、emits、v-model、slots、provide/inject;React 有 props、回调函数、children、Context、状态提升。好像只是 API 名字不同。实际使用久了会发现,两者的差异不只是“怎么传”,而是它们鼓励你如何理解组件之间的关系。
Vue 更倾向于把常见通信方式做成明确的框架能力。React 则更倾向于用 JavaScript 函数、props 和组合模式表达关系。Vue 给你更多约定好的通道,React 给你更少的框架概念但更多组合空间。

父组件向子组件传数据,Vue 和 React 都使用 props。这一点没有本质差异。
Vue 里常见写法是:
<UserCard :user="currentUser" :editable="true" />子组件中可以通过 defineProps 声明:
<script setup>
const props = defineProps({
user: Object,
editable: Boolean
})
</script>React 里则是:
<UserCard user={currentUser} editable />子组件函数直接接收参数:
function UserCard({ user, editable }) {
return <section>{user.name}</section>
}两者都很直观。区别在于,Vue 的 props 声明更像组件契约的一部分,尤其在 <script setup> 里,defineProps 和 defineEmits 往往放在组件顶部,读者很快就能看到这个组件接收什么、发出什么事件。React 也可以用 TypeScript 类型表达契约,但这是语言和工程工具提供的,不是 React 自身的运行时 API。
如果团队使用 TypeScript,这个差异会变小。Vue 可以写类型化 props,React 也可以写 props interface。区别主要在风格:Vue 把组件输入输出显式放进框架语法里,React 把组件看作普通函数,props 就是函数参数。
子组件通知父组件,是两者差异比较明显的地方。
Vue 的标准做法是 emit 事件:
<script setup>
const emit = defineEmits(['save'])
function submit() {
emit('save', { name: 'Tom' })
}
</script>
<template>
<button @click="submit">保存</button>
</template>父组件监听:
<UserEditor @save="handleSave" />React 通常把回调函数作为 props 传下去:
function UserEditor({ onSave }) {
function submit() {
onSave({ name: 'Tom' })
}
return <button onClick={submit}>保存</button>
}父组件传入:
<UserEditor onSave={handleSave} />从效果看,两者一样。但从组件关系看,Vue 更像是“子组件发出一个事件,父组件选择监听”。React 更像是“父组件给子组件一个函数,子组件在合适的时候调用”。
Vue 的事件模型让组件输出更语义化。一个通用弹窗组件可以声明 confirm、cancel、close,父组件通过事件名理解它会发生什么。React 的回调模型更直接,函数就是能力,父组件给什么,子组件就调用什么。
这也导致两边的命名习惯不同。Vue 里常见 @update:modelValue、@submit、@close。React 里常见 onChange、onSubmit、onClose。本质差异不大,但 Vue 把“事件”作为组件通信语义,React 把“函数”作为组件通信语义。
Vue 的 v-model 是很多业务开发者喜欢的能力。Vue 3 中,组件上的 v-model 本质上是 prop 和事件的语法糖,默认对应 modelValue 和 update:modelValue。例如:
<UserNameInput v-model="name" />大致等价于:
<UserNameInput
:model-value="name"
@update:model-value="name = $event"
/>Vue 3 还支持多个 v-model,适合封装表单控件和筛选组件。
React 没有同名的框架级双向绑定 API。React 常见做法是受控组件:
<input value={name} onChange={e => setName(e.target.value)} />封装组件时也是把 value 和 onChange 传进去:
<NameInput value={name} onChange={setName} />两种方式都能表达同样的事情。Vue 的 v-model 更像框架给表单场景提供的统一约定,写法简洁,尤其适合大量输入控件。React 的受控组件更显式,数据从哪里来、变化如何提交都写在 props 上,不需要额外语法糖。
实际项目里,如果表单很多,Vue 的写法会明显省代码。React 也可以通过表单库减少样板,但那是生态层面的解决方案,不是 React 核心 API 直接提供的体验。
Vue 的 slots 和 React 的 children 经常被放在一起比较。两者都能让父组件向子组件传入一段 UI,但写法和心智模型不同。
Vue 的默认插槽:
<BaseCard>
<p>这里是卡片内容</p>
</BaseCard>具名插槽:
<BaseLayout>
<template #header>标题</template>
<template #default>正文</template>
<template #footer>底部操作</template>
</BaseLayout>作用域插槽可以让子组件把数据传回给插槽内容:
<DataList :items="users">
<template #default="{ item }">
<span>{{ item.name }}</span>
</template>
</DataList>React 里最基础的是 children:
<BaseCard>
<p>这里是卡片内容</p>
</BaseCard>如果需要多个区域,常见做法是传 props:
<BaseLayout
header={<Header />}
footer={<Footer />}
>
<Main />
</BaseLayout>如果需要让子组件提供数据,可以用 render props:
<DataList
items={users}
renderItem={item => <span>{item.name}</span>}
/>Vue 的 slots 更像模板里的命名占位系统,特别适合布局组件、弹窗组件、卡片组件。React 的 children 和 render props 更像普通 JavaScript 值和函数传递,灵活度更高,也更依赖团队约定。
当组件层级很深时,逐层传 props 会很麻烦。Vue 提供 provide/inject,React 提供 Context。
Vue 的 provide/inject 允许祖先组件向后代组件提供依赖。官方文档也说明,后代组件可以不经过中间层级直接注入祖先提供的值。如果提供的是 ref,注入方会保留响应式连接。
React Context 的目标也类似:让组件树下层读取某个上层提供的值,比如主题、语言、登录用户、权限配置等。
但这两种能力都不应该被当成全局状态管理的随手替代品。它们适合传递“上下文”,不适合让任意业务状态到处读写。主题、国际化、当前表单实例、弹窗容器、权限上下文,这些是合理场景。订单详情页里所有接口数据都塞进 provide 或 Context,让子孙组件到处取,后期排查会很困难。
Vue 的 provide/inject 因为和响应式系统结合得自然,写起来很方便,也更容易被滥用。React Context 一旦 value 频繁变化,可能导致大量消费组件重新渲染,也需要拆分 Context 或配合状态管理方案。两者都要控制边界。
Vue 的通信方式更丰富,也更有框架语义。props 是输入,emits 是输出,v-model 是双向绑定约定,slots 是内容分发,provide/inject 是跨层级依赖。这套 API 对业务组件开发很友好,因为常见场景都有对应工具。
React 的通信方式更少,但更统一。props 可以传数据,也可以传函数,也可以传 JSX,也可以传 render 函数;children 也是 props;Context 是跨层级 props。React 的组件通信几乎都能回到一句话:通过 props 和组合传递数据与行为。
这就是两者风格差异。Vue 把不同通信意图分成不同 API,语义清楚,学习时要记住更多概念。React 把很多事情统一到 JavaScript 和 props 里,概念少,组合自由,但大型项目更需要规范。
Vue 项目里,父子关系明确的组件优先用 props 和 emits。表单控件优先遵守 v-model 约定。布局型组件用 slots。跨层级依赖用 provide/inject,但不要把它当成随处可写的状态仓库。如果状态会被多个业务模块共同修改,应该考虑 Pinia 或更清晰的模块状态设计。
React 项目里,简单父子通信优先用 props 和回调。通用容器组件用 children 或具名 props。需要把渲染权交给调用方时,可以用 render props,但不要过度设计。Context 适合低频变化的上下文,不适合把所有列表数据和表单字段都塞进去。如果状态更新频繁、消费范围广,要考虑拆 Context、局部状态、外部 store 或专门状态管理库。
Vue 3 和 React 在组件通信上的差异,不是 API 谁多谁少的问题。Vue 更愿意把常见组件关系变成框架级语义,让代码一眼能看出“这是输入、这是事件、这是插槽、这是注入”。React 更愿意保持组件是函数、props 是参数的基本模型,让开发者用 JavaScript 组合出各种关系。
如果你的团队需要清晰约定和较低维护成本,Vue 的通信模型会很舒服。如果你的团队需要高度灵活的抽象能力,React 的 props 和组合模型会更有空间。
不要只看“哪个写法更短”。通信方式的本质,是组件关系的表达方式。组件关系设计清楚,Vue 和 React 都能写得稳;组件关系混乱,API 再漂亮也救不了项目。
Vue 的组件输出通常通过 defineEmits 显式声明,父组件监听事件:
<!-- ConfirmDialog.vue -->
<script setup>
defineProps({
title: String
})
const emit = defineEmits(['confirm', 'cancel'])
</script>
<template>
<section class="dialog">
<h3>{{ title }}</h3>
<button @click="emit('cancel')">取消</button>
<button @click="emit('confirm')">确认</button>
</section>
</template><ConfirmDialog
title="删除用户"
@cancel="visible = false"
@confirm="removeUser"
/>React 的同类组件通常通过回调 props 暴露动作:
function ConfirmDialog({ title, onCancel, onConfirm }) {
return (
<section className="dialog">
<h3>{title}</h3>
<button onClick={onCancel}>取消</button>
<button onClick={onConfirm}>确认</button>
</section>
)
}<ConfirmDialog
title="删除用户"
onCancel={() => setVisible(false)}
onConfirm={removeUser}
/>跨层级共享配置时,Vue 可以提供响应式值:
// parent setup
import { provide, readonly, ref } from 'vue'
const theme = ref('light')
provide('theme', readonly(theme))
provide('setTheme', value => {
theme.value = value
})// deep child setup
import { inject } from 'vue'
const theme = inject('theme')
const setTheme = inject('setTheme')React 则通常通过 Context Provider 包一层:
const ThemeContext = createContext(null)
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}function ThemeButton() {
const { theme, setTheme } = useContext(ThemeContext)
return <button onClick={() => setTheme('dark')}>{theme}</button>
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。