首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Vue 3 和 React 的第三个差异:组件通信不是传值方式不同,而是组件关系的设计不同

Vue 3 和 React 的第三个差异:组件通信不是传值方式不同,而是组件关系的设计不同

原创
作者头像
peace-free
发布2026-06-02 10:42:26
发布2026-06-02 10:42:26
1720
举报
文章被收录于专栏:Vue3Vue3

Vue 3 和 React 都是组件化框架。只要项目稍微复杂一点,就绕不开组件通信:父组件给子组件传数据,子组件通知父组件,兄弟组件共享状态,深层组件读取全局配置,通用组件开放插槽或渲染入口。

表面上看,Vue 有 props、emits、v-model、slots、provide/inject;React 有 props、回调函数、children、Context、状态提升。好像只是 API 名字不同。实际使用久了会发现,两者的差异不只是“怎么传”,而是它们鼓励你如何理解组件之间的关系。

Vue 更倾向于把常见通信方式做成明确的框架能力。React 则更倾向于用 JavaScript 函数、props 和组合模式表达关系。Vue 给你更多约定好的通道,React 给你更少的框架概念但更多组合空间。

image-20260516093836035
image-20260516093836035

一、父传子:两边都靠 props,但 Vue 的声明感更强

父组件向子组件传数据,Vue 和 React 都使用 props。这一点没有本质差异。

Vue 里常见写法是:

代码语言:javascript
复制
<UserCard :user="currentUser" :editable="true" />

子组件中可以通过 defineProps 声明:

代码语言:javascript
复制
<script setup>
const props = defineProps({
  user: Object,
  editable: Boolean
})
</script>

React 里则是:

代码语言:javascript
复制
<UserCard user={currentUser} editable />

子组件函数直接接收参数:

代码语言:javascript
复制
function UserCard({ user, editable }) {
  return <section>{user.name}</section>
}

两者都很直观。区别在于,Vue 的 props 声明更像组件契约的一部分,尤其在 <script setup> 里,definePropsdefineEmits 往往放在组件顶部,读者很快就能看到这个组件接收什么、发出什么事件。React 也可以用 TypeScript 类型表达契约,但这是语言和工程工具提供的,不是 React 自身的运行时 API。

如果团队使用 TypeScript,这个差异会变小。Vue 可以写类型化 props,React 也可以写 props interface。区别主要在风格:Vue 把组件输入输出显式放进框架语法里,React 把组件看作普通函数,props 就是函数参数。

二、子传父:Vue 用事件,React 用回调

子组件通知父组件,是两者差异比较明显的地方。

Vue 的标准做法是 emit 事件:

代码语言:javascript
复制
<script setup>
const emit = defineEmits(['save'])

function submit() {
  emit('save', { name: 'Tom' })
}
</script>

<template>
  <button @click="submit">保存</button>
</template>

父组件监听:

代码语言:javascript
复制
<UserEditor @save="handleSave" />

React 通常把回调函数作为 props 传下去:

代码语言:javascript
复制
function UserEditor({ onSave }) {
  function submit() {
    onSave({ name: 'Tom' })
  }

  return <button onClick={submit}>保存</button>
}

父组件传入:

代码语言:javascript
复制
<UserEditor onSave={handleSave} />

从效果看,两者一样。但从组件关系看,Vue 更像是“子组件发出一个事件,父组件选择监听”。React 更像是“父组件给子组件一个函数,子组件在合适的时候调用”。

Vue 的事件模型让组件输出更语义化。一个通用弹窗组件可以声明 confirmcancelclose,父组件通过事件名理解它会发生什么。React 的回调模型更直接,函数就是能力,父组件给什么,子组件就调用什么。

这也导致两边的命名习惯不同。Vue 里常见 @update:modelValue@submit@close。React 里常见 onChangeonSubmitonClose。本质差异不大,但 Vue 把“事件”作为组件通信语义,React 把“函数”作为组件通信语义。

三、双向绑定:Vue 有内建约定,React 倾向受控组件

Vue 的 v-model 是很多业务开发者喜欢的能力。Vue 3 中,组件上的 v-model 本质上是 prop 和事件的语法糖,默认对应 modelValueupdate:modelValue。例如:

代码语言:javascript
复制
<UserNameInput v-model="name" />

大致等价于:

代码语言:javascript
复制
<UserNameInput
  :model-value="name"
  @update:model-value="name = $event"
/>

Vue 3 还支持多个 v-model,适合封装表单控件和筛选组件。

React 没有同名的框架级双向绑定 API。React 常见做法是受控组件:

代码语言:javascript
复制
<input value={name} onChange={e => setName(e.target.value)} />

封装组件时也是把 valueonChange 传进去:

代码语言:javascript
复制
<NameInput value={name} onChange={setName} />

两种方式都能表达同样的事情。Vue 的 v-model 更像框架给表单场景提供的统一约定,写法简洁,尤其适合大量输入控件。React 的受控组件更显式,数据从哪里来、变化如何提交都写在 props 上,不需要额外语法糖。

实际项目里,如果表单很多,Vue 的写法会明显省代码。React 也可以通过表单库减少样板,但那是生态层面的解决方案,不是 React 核心 API 直接提供的体验。

四、插槽和 children:一个偏声明式占位,一个偏组合式传入

Vue 的 slots 和 React 的 children 经常被放在一起比较。两者都能让父组件向子组件传入一段 UI,但写法和心智模型不同。

Vue 的默认插槽:

代码语言:javascript
复制
<BaseCard>
  <p>这里是卡片内容</p>
</BaseCard>

具名插槽:

代码语言:javascript
复制
<BaseLayout>
  <template #header>标题</template>
  <template #default>正文</template>
  <template #footer>底部操作</template>
</BaseLayout>

作用域插槽可以让子组件把数据传回给插槽内容:

代码语言:javascript
复制
<DataList :items="users">
  <template #default="{ item }">
    <span>{{ item.name }}</span>
  </template>
</DataList>

React 里最基础的是 children

代码语言:javascript
复制
<BaseCard>
  <p>这里是卡片内容</p>
</BaseCard>

如果需要多个区域,常见做法是传 props:

代码语言:javascript
复制
<BaseLayout
  header={<Header />}
  footer={<Footer />}
>
  <Main />
</BaseLayout>

如果需要让子组件提供数据,可以用 render props:

代码语言:javascript
复制
<DataList
  items={users}
  renderItem={item => <span>{item.name}</span>}
/>

Vue 的 slots 更像模板里的命名占位系统,特别适合布局组件、弹窗组件、卡片组件。React 的 children 和 render props 更像普通 JavaScript 值和函数传递,灵活度更高,也更依赖团队约定。

五、跨层级通信:Vue provide/inject 和 React Context 都不能滥用

当组件层级很深时,逐层传 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 强调通信语义,React 强调组合能力

Vue 3 和 React 在组件通信上的差异,不是 API 谁多谁少的问题。Vue 更愿意把常见组件关系变成框架级语义,让代码一眼能看出“这是输入、这是事件、这是插槽、这是注入”。React 更愿意保持组件是函数、props 是参数的基本模型,让开发者用 JavaScript 组合出各种关系。

如果你的团队需要清晰约定和较低维护成本,Vue 的通信模型会很舒服。如果你的团队需要高度灵活的抽象能力,React 的 props 和组合模型会更有空间。

不要只看“哪个写法更短”。通信方式的本质,是组件关系的表达方式。组件关系设计清楚,Vue 和 React 都能写得稳;组件关系混乱,API 再漂亮也救不了项目。

代码补充:事件、插槽和 Context 的实际写法

Vue 的组件输出通常通过 defineEmits 显式声明,父组件监听事件:

代码语言:javascript
复制
<!-- 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>
代码语言:javascript
复制
<ConfirmDialog
  title="删除用户"
  @cancel="visible = false"
  @confirm="removeUser"
/>

React 的同类组件通常通过回调 props 暴露动作:

代码语言:javascript
复制
function ConfirmDialog({ title, onCancel, onConfirm }) {
  return (
    <section className="dialog">
      <h3>{title}</h3>
      <button onClick={onCancel}>取消</button>
      <button onClick={onConfirm}>确认</button>
    </section>
  )
}
代码语言:javascript
复制
<ConfirmDialog
  title="删除用户"
  onCancel={() => setVisible(false)}
  onConfirm={removeUser}
/>

跨层级共享配置时,Vue 可以提供响应式值:

代码语言:javascript
复制
// parent setup
import { provide, readonly, ref } from 'vue'

const theme = ref('light')
provide('theme', readonly(theme))
provide('setTheme', value => {
  theme.value = value
})
代码语言:javascript
复制
// deep child setup
import { inject } from 'vue'

const theme = inject('theme')
const setTheme = inject('setTheme')

React 则通常通过 Context Provider 包一层:

代码语言:javascript
复制
const ThemeContext = createContext(null)

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}
代码语言:javascript
复制
function ThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext)
  return <button onClick={() => setTheme('dark')}>{theme}</button>
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、父传子:两边都靠 props,但 Vue 的声明感更强
  • 二、子传父:Vue 用事件,React 用回调
  • 三、双向绑定:Vue 有内建约定,React 倾向受控组件
  • 四、插槽和 children:一个偏声明式占位,一个偏组合式传入
  • 五、跨层级通信:Vue provide/inject 和 React Context 都不能滥用
  • 六、组件通信背后的取舍
  • 七、实践建议
  • 八、结论:Vue 强调通信语义,React 强调组合能力
  • 代码补充:事件、插槽和 Context 的实际写法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档