Vue vs React 核心差异全对比

Vue vs React 核心差异全对比:一份从设计哲学到指令、事件、插槽的实战速查表

前言:差异不在语法,在哲学

很多人第一次从 Vue 迁到 React,会经历这样的困惑:

js 复制代码
emit('add', item)   // Vue:子组件发了个事件
props.onAdd(item)   // React:子组件调了个函数

看起来只是写法不同,但背后是两套完全不同的心智模型。本文按 「根因 → 高频场景 → 迁移清单 → Vue 指令全景」 的顺序整理,既可以当科普读,也可以当迁移时的速查手册。


一、先看根因:两种设计哲学

所有表面差异,都能追溯到下面这张表:

维度 Vue React
编程范式 模板 DSL + 指令(编译期优化) JSX = 纯 JS 表达式(运行时协调)
DOM 操作 指令/模板声明,框架接管 手写 JS + Hooks + ref
心智模型 "声明做什么" "用 JS 表达做什么"
响应式 Proxy 自动追踪依赖 setState 触发重渲染 + 手动依赖数组

一句话总结:

Vue 用指令屏蔽细节,React 用 JS 暴露一切。

理解了这句话,后面的所有对照表都只是它的注脚。


二、子传父:Vue 是"发事件",React 是"传函数"

Vue React
子 emit('send-msg', data) 子调用 props.onSendMsg(data)
父 @send-msg="fn" 父 <Child onSendMsg={fn} />
需 defineEmits(['send-msg']) 声明 无需声明,靠约定 + TS 类型
事件名 kebab-case Prop 名 camelCase + on 前缀
js 复制代码
// 核心区别:Vue 是"事件",React 是"传函数"
emit('add', item)    // Vue
props.onAdd(item)    // React

在 Vue 里,事件是一个有名字的"通道",需要显式声明;在 React 里,回调本身就是 prop,类型系统(TS)替代了事件声明。


三、事件绑定:从 @click 到 onClick

Vue React
@click="fn" onClick={fn}
@click="fn('a', $event)" onClick={e => fn('a', e)}
修饰符 .stop / .prevent 手动 e.stopPropagation() / e.preventDefault()
.self if (e.target !== e.currentTarget) return
.enter / .esc 按键修饰符 if (e.key === 'Enter')
.once 用 ref 标记 / 一次性函数
.native(Vue 2) 直接绑 onClick(React 无此区分)

从这张表能看出一个规律:Vue 用修饰符把常见 DOM 操作语法化,React 则把它交还给原生事件对象。


四、指令 ↔ JS 表达式:一张表打通

这是 Vue 开发者迁 React 时查得最频繁的一张表:

Vue 指令 React 等价写法
v-if="c" {c && <A />} 或 {c ? <A/> : <B/>}
v-else-if / v-else 嵌套三元 / 提前 return / 查表对象
v-show="c" style={{ display: c ? '' : 'none' }}
v-for="i in list" list.map(i => <X key={i.id} />)
v-for 遍历对象 Object.entries(obj).map(([k, v]) => ...)
:prop="v" prop={v}
v-bind="obj" {...obj}
v-model="v" value={v} onChange={e => setV(e.target.value)}
v-html="s" dangerouslySetInnerHTML={{ __html: s }}
v-text {text}
v-slot children / JSX prop / render props
v-once / v-memo React.memo / useMemo / useCallback
v-pre / v-cloak 无对应(JSX 不做插值编译;无闪烁问题)
:class="obj" className={clsx(obj)}(社区库)
:style="obj" style={{ color: 'red' }}(驼峰)

一个值得注意的点:v-pre / v-cloak 在 React 中没有对应物 ------因为 JSX 根本不存在"模板插值编译"这一阶段,自然也就没有 {{ }} 闪烁问题。这就是"编译期优化"与"运行时协调"的差异体现。


五、v-model:Vue 的语法糖,React 的手工活

Vue React
v-model="text" value={text} + onChange
.trim / .number / .lazy 在 onChange 里手动 trim() / Number() / 换 onBlur
组件级 v-model 约定 value + onChange 两个 prop
多字段 v-model:name 一个 form state + updateField('name')
jsx 复制代码
// React 标准受控组件写法
<input value={text} onChange={e => setText(e.target.value)} />

💡 React 官方没有双向绑定语法糖 ,这是 Vue 转 React 最不适应的地方。Vue 的 v-model 本质就是 :value + @input 的语法糖,React 只是把这层糖拆开还给你。


六、插槽:从 slot 到 children / Render Props

Vue React
默认插槽 <slot /> props.children
具名插槽 <slot name="header"> <Card header={<h1/>} />(JSX 当 prop 传)
作用域插槽 #default="{ item }" Render Props :renderItem={item => <b>{item}</b>}
<template #xxx> 声明插槽内容 直接把 JSX 作为 prop 传入

React 没有"插槽"这个专门概念,因为 JSX 本身就是一等公民的表达式------它能出现在任何 JS 能出现的地方,于是"插槽"就退化成了"传值"。


七、自定义指令 → 自定义 Hook

Vue 自定义指令 React 自定义 Hook
app.directive('focus', {...}) 注册 Hook 直接 import 使用
mounted(el, binding) useEffect(() => {...}, [])
updated(el, binding) useEffect(() => {...}, [value])
unmounted useEffect 返回的清理函数
binding.value Hook 参数
binding.modifiers 配置对象 / 额外参数
需要 el(DOM 引用) useRef + ref={}

一个典型的等价改写------自动聚焦:

js 复制代码
// Vue:v-focus 指令
// React:
function AutoFocusInput(props) {
  const ref = useRef(null)
  useEffect(() => ref.current?.focus(), [])
  return <input ref={ref} {...props} />
}

Vue 的指令是"给元素挂行为",React 的 Hook 是"在组件里跑逻辑再绑到 ref 上"------前者是声明式的 DOM 扩展 ,后者是可组合的逻辑单元。


八、生命周期对照

概念 Vue (Composition API) React Hooks
挂载 onMounted useEffect(() => {}, [])
更新 onUpdated useEffect(() => {}, [deps])
卸载 onUnmounted useEffect 返回清理函数
副作用 watchEffect / watch useEffect + 依赖数组
计算属性 computed useMemo
组件级共享状态 provide/inject Context + useContext

React 用一个 useEffect + 依赖数组覆盖了 Vue 的 mounted / updated / unmounted 三个钩子,这种"收束"是 React 的一贯风格。


九、性能优化:自动 vs 手动

目标 Vue React
组件不重渲染 自动依赖追踪(无需管) React.memo
缓存昂贵计算 computed useMemo(() => fn(), [deps])
缓存函数引用 无需管 useCallback(fn, [deps])
缓存 JSX 片段 v-memo="[deps]" useMemo(() => <JSX/>, [deps])
只渲染一次 v-once useState / useRef 初始化

Vue 自动追踪依赖,优化空间小;React 需要手动管理 memo / 依赖,容易踩"引用不稳定"的坑。


十、10 条"一句话"差异总结

  1. 子传父 :Vue emit 事件,React 回调 prop。
  2. 事件 :Vue @click,React onClick,驼峰、传引用。
  3. 修饰符 :Vue 有 .stop/.prevent 等,React 全部手写。
  4. 双向绑定 :Vue v-model,React 手写 value + onChange。
  5. 条件/循环 :Vue 指令,React 三元 / && / .map。
  6. key 必填:两者规则完全一致,都不建议用索引。
  7. 插槽 :Vue v-slot,React JSX 当 prop / children / render props。
  8. 自定义指令 :Vue directives,React 自定义 Hook + useEffect。
  9. 优化 :Vue 自动依赖追踪,React 手动 memo / useMemo / useCallback。
  10. 更新状态 :Vue 直接赋值,React setState + 函数式更新 prev => ... 防旧值。

附录:Vue 指令全景速查

既然对比的起点是"指令",那就有必要把 Vue 指令本身讲清楚------它是 Vue 区别于 React 最鲜明的语法特征。

1. 指令的本质与结构

Vue 指令是带有 v- 前缀的特殊 HTML 属性,本质是将响应式数据与 DOM 行为连接起来的声明式语法 ,让你无需手动 querySelector 或 addEventListener,页面就能随数据自动变化。

一个完整指令可以拆解为:

arduino 复制代码
v-指令名:参数.修饰符="表达式"

例如 v-on:click.stop="handleClick" 中,v-on 是指令名,click 是参数,stop 是修饰符,handleClick 是表达式。

2. 内置指令全览(以 Vue 3 为准)

📝 内容渲染

指令 作用 关键点
v-text 更新元素的 textContent 会覆盖元素内所有原有内容
v-html 更新元素的 innerHTML 存在 XSS 风险,切勿用在用户输入上;scoped 样式不生效
v-pre 跳过该元素及其子元素的编译 用于显示原始 {{ }},提升静态内容编译速度
v-once 只渲染一次 数据后续变化不再更新,用于性能优化
v-memo Vue 3.2+,缓存模板片段 传依赖数组,仅在依赖变化时重渲染,用于大型列表极致优化

🔀 条件渲染

指令 作用 关键点
v-if 根据真假值销毁或重建元素/组件 切换开销大,适合初始为假或很少切换 的场景;可配合 <template>
v-else-if / v-else 构成 v-if 的链式分支 前一兄弟元素必须是 v-if 或 v-else-if
v-show 通过 display 切换可见性 元素始终在 DOM 中,切换开销小,适合频繁切换

🔄 列表渲染

指令 作用 关键点
v-for 遍历数组、对象、数字或字符串渲染列表 必须提供唯一 :key(推荐数据 ID 而非索引),避免状态错乱

🔗 数据绑定

指令 作用 关键点
v-bind 动态绑定一个或多个属性/Prop 简写 :。可绑 class、style、href,支持对象语法
v-model 表单/组件上的双向绑定 本质是 :value + @input 的语法糖;Vue 3 支持多个 v-model 和自定义修饰符

⚡ 事件监听

指令 作用 关键点
v-on 监听 DOM 事件或组件自定义事件 简写 @,可配合大量事件修饰符

🎯 其他关键指令

指令 作用 关键点
v-slot 声明具名插槽或作用域插槽 简写 #,通常用在 <template> 上
v-cloak 保持在元素上直到组件实例编译结束 配合 [v-cloak] { display: none } 解决插值闪烁

3. 修饰符

修饰符是以 . 指明的特殊后缀,用于指出指令应以特殊方式绑定。

v-on(@)事件修饰符

修饰符 作用
.stop 阻止事件冒泡(event.stopPropagation())
.prevent 阻止默认行为(event.preventDefault())
.self 仅当事件由元素自身触发时才执行
.capture 使用事件捕获模式
.once 事件只触发一次
.passive 以 { passive: true } 添加监听器,提升滚动性能

按键与系统修饰符

类别 修饰符
按键别名 .enter, .tab, .delete(捕获"删除"和"退格"), .esc, .space, .up, .down, .left, .right
系统按键 .ctrl, .alt, .shift, .meta(Mac 上为 Command)
精确匹配 .exact(仅当只有被按下的系统按键组合时触发)

v-bind(:)与 v-model 的修饰符

修饰符 所属指令 作用
.prop v-bind 强制作为 DOM property 而非 attribute
.camel v-bind 将 kebab-case 属性名转 camelCase(常用于 SVG)
.sync v-bind Vue 2 语法 ,Vue 3 已被 v-model:propName 替代
.lazy v-model 同步时机从 input 改为 change
.number v-model 自动转换为数字类型
.trim v-model 自动去除首尾空白

⚠️ Vue 2 特有 :.native 修饰符用于监听组件根元素的原生事件,Vue 3 已移除。

4. 自定义指令

当需要复用的逻辑涉及底层 DOM 操作(自动聚焦、懒加载、集成第三方 DOM 库)时,用自定义指令。

注册方式

  • 全局 :app.directive('focus', { ... })
  • 局部 :组件 directives 选项中注册
  • <script setup> :任何以 v 开头的驼峰变量都可直接用(const vFocus = {...} 对应 v-focus)

钩子函数(生命周期)

钩子(Vue 3) 对应 Vue 2 钩子 触发时机与用途
created (无) 元素属性/事件监听器应用前调用(Vue 3 新增)
beforeMount bind 元素插入 DOM 前,适合 CSS 相关操作
mounted inserted 最常用,元素已挂载,可安全操作 DOM
beforeUpdate (无直接对应) 元素更新前调用
updated componentUpdated 元素及子节点更新后调用
beforeUnmount (无直接对应) 元素卸载前调用(Vue 3 新增),适合清理
unmounted unbind 元素卸载后调用,必须在此清理资源

钩子参数 :每个钩子会收到 el(DOM 元素)、binding(含 value、oldValue、arg、modifiers)、vnode 等参数。


写在最后

如果把这份对照表压缩成一句话:

Vue 把常见模式编译成指令,React 把一切还原成 JavaScript。

所以迁 React 时,你不是在"换 API",而是在把指令的语义手动展开成 JS 表达式 ;反过来迁 Vue 时,你是在把 JS 逻辑收敛成声明式语法。

内置指令覆盖了绝大多数日常场景,修饰符是对指令行为的微调,而自定义指令则是遇到纯 DOM 操作复用时的"逃生舱"------它处理的正是 v-bind 这类声明式语法无法优雅解决的问题。

相关推荐
李纲明1 小时前
微信小程序 web-view 打开空白或提示未配置业务域名:校验文件、HTTPS 与跳转域名排查
前端·微信小程序·https·web-view·小程序开发·业务域名
涛涛ing1 小时前
一个框架为了“不水合”,把HTML注释都删了
前端
计算机魔术师2 小时前
AI渗透框架3小时破云,中国黑客组织正在升级打法
前端
蒲公英内测分发2 小时前
智能手环出海测试,海外合作方分散在不同国家,Android 测试包怎么持续分发和更新?
android·前端·人工智能·测试工具·智能手表
朱涛的自习室2 小时前
高价软件已死,我让 AI 开启了「平替计划」(Day 7 / 7)
android·前端·github
沙蒿同学2 小时前
Wails v3 实战:一个桌面提醒客户端,从 v2 升到 v3 的全过程
前端·后端·github
inquisiter3 小时前
KernelGpt的部分尝试
java·前端·apache
the局外人3 小时前
用 TRAE 把 Markdown文档 搬到微信公众号
前端
Autumn_ing3 小时前
AI原型设计工具怎么选?可编辑原型与可运行前端适合什么场景
前端·小程序·产品经理·代码·codex·墨刀ai客户端·ai原型