01 框架哲学对比 VueReact

Vue — 渐进式框架

核心 Nuxt / 全家桶 Pinia + Router 组合式 API 渐进增强 可仅用核心
约定优于配置 开箱即用
Template-first 模板优先
渐进式 按需引入功能
响应式驱动 自动依赖追踪

React — UI 库

UI Redux Router Hooks Next.js SSR 一切皆 JavaScript
一切皆JS UI = f(state)
JSX-first JS 中写模板
组合模式 自由组装生态
单向数据流 显式更新
Vue 理念
对比维度
React 理念
渐进式框架,核心 + 可选工具链
定位
UI 渲染库,生态自由组合
模板 + 指令系统,关注点分离
视图表达
JSX,JS 和 UI 融合
Proxy 自动追踪,开发者无感
响应式
setState 手动触发,显式更新
SFC 单文件组件 (.vue)
组件格式
函数组件 (.jsx/.tsx)
官方维护全家桶,开箱即用
生态策略
社区驱动,百花齐放
02 响应式 vs 单向数据流
数据流对比图

Vue — Proxy 响应式

State View Proxy 自动渲染 依赖自动追踪
修改数据 → Proxy 拦截 → 自动触发视图更新
开发者无需手动触发

React — setState 驱动

setState() Re-render Virtual DOM 触发调度 Diff 算法比对
调用 setState → 调度更新 → Virtual DOM Diff → 最小化更新
开发者必须显式调用 setState
计数器示例对比
Vue 3 — Composition API
<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)

function increment() {
  count.value++   // 自动触发更新,无需手动
}
</script>

<template>
  <p>计数: {{ count }}</p>
  <p>双倍: {{ double }}</p>
  <button @click="increment">+1</button>
</template>
React — Hooks
import { useState, useMemo } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  const double = useMemo(
    () => count * 2, [count]
  )

  function increment() {
    setCount(c => c + 1) // 显式调用 setter
  }

  return (
    <>
      <p>计数: {count}</p>
      <p>双倍: {double}</p>
      <button onClick={increment}>+1</button>
    </>
  )
}
Vue 响应式
对比点
React 状态管理
ref() / reactive() 声明,直接赋值触发更新
声明方式
useState() 返回 [value, setter],必须调用 setter
Proxy 自动追踪依赖,精确更新
更新机制
setState → 整组件 re-render → Virtual DOM Diff
computed() 自动缓存
派生计算
useMemo() 手动声明依赖数组
默认细粒度更新,无需手动优化
性能优化
需 React.memo / useMemo / useCallback 手动优化
03 模板语法 vs JSX — 10 大常见模式
核心差异:Vue 使用 HTML 模板 + 指令(v-if / v-for),React 使用 JSX(三元表达式 / map),模板更声明式,JSX更灵活。
① 条件渲染
Vue
<div v-if="isLoggedIn">欢迎回来</div>
<div v-else>请先登录</div>

<!-- v-show: 切换 display -->
<p v-show="visible">仅切换显示/隐藏</p>
React
<div>{isLoggedIn ? '欢迎回来' : '请先登录'}</div>

{/* && 短路写法 */}
{isLoggedIn && <div>欢迎回来</div>}
② 列表渲染
Vue
<ul>
  <li v-for="item in list"
      :key="item.id">
    {{ item.name }}
  </li>
</ul>
React
<ul>
  {list.map(item => (
    <li key={item.id}>
      {item.name}
    </li>
  ))}
</ul>
③ 双向绑定 / 表单
Vue
<!-- v-model 语法糖 -->
<input v-model="text" />
<input v-model.number="age" />

<!-- 等价于 -->
<input
  :value="text"
  @input="text = $event.target.value"
/>
React
{/* 受控组件,手动绑定 */}
<input
  value={text}
  onChange={e => setText(e.target.value)}
/>

{/* 数字转换 */}
<input
  value={age}
  onChange={e => setAge(Number(e.target.value))}
/>
④ 事件处理
Vue
<button @click="handleClick">点击</button>
<button @click="handleClick(item.id)">
  传参
</button>
<input @keyup.enter="submit" />
React
<button onClick={handleClick}>点击</button>
<button onClick handleClick(item.id)}>
  传参
</button>
<input onKeyDown e.key === 'Enter' && submit()} />
⑤ 动态属性绑定
Vue
<img :src="imgUrl" :alt="title" />
<div :class="{ active: isActive, 'text-bold': isBold }">
<div :style="{ color: fontColor, fontSize: size + 'px' }">
React
<img src={imgUrl} alt={title} />
<div className={`${isActive ? 'active' : ''} ${isBold ? 'text-bold' : ''}`}>
<div style={{ color: fontColor, fontSize: size + 'px' }}>
⑥ 计算属性
Vue
const fullName = computed(() =>
  firstName.value + ' ' + lastName.value
)

// 模板中直接用 {{ fullName }}
// 自动缓存,依赖不变不重算
React
const fullName = useMemo(() =>
  firstName + ' ' + lastName
, [firstName, lastName])

// JSX 中直接用 {fullName}
// 需手动声明依赖数组
⑦ 插槽 / 子内容
Vue
<!-- 父组件 -->
<Card>
  <template #header>标题</template>
  <template #default>内容</template>
  <template #footer>底部</template>
</Card>

<!-- 子组件 Card.vue -->
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
React
{/* 父组件 */}
<Card
  header={<h2>标题</h2>}
  footer={<p>底部</p>}
>
  内容
</Card>

{/* 子组件 Card */}
function Card({ header, footer, children }) {
  return (
    <>{header}{children}{footer}</>
  )
}
⑧ 侦听器 / 副作用
Vue
watch(count, (newVal, oldVal) => {
  console.log(newVal)
})

// 立即执行
watchEffect(() => {
  fetchData(keyword.value)
})
React
useEffect(() => {
  console.log(count)
}, [count])

// 每次渲染都执行
useEffect(() => {
  fetchData(keyword)
}) // 无依赖数组
⑨ 模板引用 (Refs)
Vue
<input ref="inputRef" />

const inputRef = ref(null)
onMounted(() => {
  inputRef.value.focus()
})
React
const inputRef = useRef(null)

<input ref={inputRef} />

useEffect(() => {
  inputRef.current.focus()
}, [])
⑩ 组件定义
Vue
<!-- UserCard.vue -->
<script setup lang="ts">
defineProps<{ name: string; age: number }>()
const emit = defineEmits<{
  (e: 'click', id: number): void
}>()
</script>

<template>
  <div @click="emit('click', 1)">
    {{ name }} / {{ age }}
  </div>
</template>
React
// UserCard.tsx
interface Props {
  name: string; age: number
  onClick: (id: number) => void
}

function UserCard({ name, age, onClick }: Props) {
  return (
    <div onClick onClick(1)}>
      {name} / {age}
    </div>
  )
}

export default UserCard
04 组件通信对照
Vue 方式
通信模式
React 方式
Props / Emit
defineProps 接收父组件数据
defineEmits 向父组件发事件
// 子组件
const props = defineProps(['msg'])
const emit = defineEmits(['update'])
emit('update', newValue)
父子通信
↓ ↓ ↓
Props / Callback
Props 传递数据和回调函数
子组件调用回调 → 通知父组件
// 子组件
function Child({ msg, onUpdate }) {
  onUpdate(newValue)
}
Provide / Inject
祖先 provide('key', value)
后代 inject('key')
// 祖先
provide('theme', 'dark')
// 后代
const theme = inject('theme')
跨层级
↓ ↓ ↓
Context
createContext 创建上下文
useContext 消费上下文
const Ctx = createContext('light')
// 祖先
<Ctx.Provider value="dark">
// 后代
const theme = useContext(Ctx)
Pinia / Vuex
全局状态管理,组件外可使用
defineStoreuseStore
const useUser = defineStore('user', () => {
  const name = ref('张三')
  return { name }
})
全局状态
↓ ↓ ↓
Redux / Zustand
Redux: reducer + dispatch
Zustand: 更轻量的 Hooks API
// Zustand
const useStore = create((set) => ({
  name: '张三',
  setName: (n) => set({ name: n })
}))
EventBus / mitt
Vue 3 推荐使用 mitt 库
任意组件间事件通信
import mitt from 'mitt'
const bus = mitt()
bus.emit('refresh', data)
bus.on('refresh', handler)
任意组件
↓ ↓ ↓
Custom Events / 状态提升
React 不推荐全局事件总线
通常状态提升到最近公共祖先
// 状态提升到公共父组件
function Parent() {
  const [data, setData] = useState()
  return <>
    <A onSend={setData} />
    <B data={data} />
  </>
}
05 生命周期对照
核心转换公式:Vue 3 Composition API 的生命周期钩子几乎可以一一映射到 React 的 useEffect,但心智模型不同 — Vue 是钩子驱动,React 是依赖驱动。
生命周期时间线对照

Vue 3 生命周期

setup() — 组件初始化(Composition API 入口)
onBeforeMount() — 挂载前,DOM 未就绪
onMounted() — 挂载完成,可访问 DOM
onBeforeUpdate() — 响应式数据变化,DOM 更新前
onUpdated() — DOM 更新完成
onBeforeUnmount() — 卸载前,清理资源
onUnmounted() — 卸载完成
onErrorCaptured() — 捕获后代错误

React Hooks 等价

函数体执行 — 每次渲染都运行
useEffect — 无直接对应(少用)
useEffect(() => {}, []) — 空依赖 = 仅挂载时执行
useEffect 清理函数 — 更新前执行上一次的 cleanup
useEffect(() => {}, [deps]) — 依赖变化时执行
useEffect 的 return cleanup — 卸载前执行
组件函数不再执行 = 已卸载
ErrorBoundary 类组件 — getDerivedStateFromError
对照代码示例
Vue 3 生命周期
import {
  onMounted,
  onUpdated,
  onUnmounted
} from 'vue'

onMounted(() => {
  console.log('组件已挂载')
  // 获取 DOM、发请求、绑定事件
})

onUpdated(() => {
  console.log('组件已更新')
})

onUnmounted(() => {
  console.log('组件已卸载')
  // 清理定时器、取消订阅
})
React Hooks 等价
import { useEffect } from 'react'

// ≈ onMounted + onUnmounted
useEffect(() => {
  console.log('组件已挂载')

  return () => {
    console.log('组件将卸载')
    // 清理定时器、取消订阅
  }
}, []) // 空依赖 = 仅挂载/卸载

// ≈ onUpdated
useEffect(() => {
  console.log('依赖变化,已更新')
}, [dep1, dep2]) // 依赖变化时执行
Vue
阶段
React
setup()
创建
函数体(每次渲染执行)
onMounted()
挂载
useEffect(() => {}, [])
onUpdated()
更新
useEffect(() => {}, [deps])
onUnmounted()
卸载
useEffect 的 return 清理函数
watch() / watchEffect()
侦听
useEffect() (依赖数组控制)
nextTick()
下一帧
flushSync() 或 queueMicrotask
06 常用生态对照表
Vue 生态
功能领域
React 生态
Vue Router
官方路由,支持守卫、动态路由
路由
React Router
声明式路由,v6 推荐 Hooks
Pinia
组合式 API,轻量直观,TS 友好
状态管理
Redux Toolkit / Zustand
RTK 标准化 / Zustand 极简
Element Plus
企业级 UI,国内主流
UI 组件库
Ant Design
蚂蚁出品,全球广泛使用
VeeValidate
声明式表单校验
表单
React Hook Form
非受控模式,性能优秀
Axios
HTTP 客户端,拦截器灵活
HTTP 请求
Axios / SWR
Axios 或 SWR 数据缓存方案
Vite (官方)
极速构建,Vue 官方推荐
构建工具
Vite / CRA
Vite 或 Create React App
Nuxt.js
SSR/SSG 全栈框架
SSR 框架
Next.js
SSR/SSG/ISR,生态最成熟
Vitest
Vite 原生测试框架
测试
Jest
最流行的 JS 测试框架
UniApp
一套代码多端运行
移动端
React Native
原生渲染,性能接近原生
07 性能优化对照
面试关键点:Vue 默认细粒度更新(Proxy 精确追踪),React 默认整组件 re-render(需手动优化)。理解这个根本差异是面试加分项。
Vue 优化
优化策略
React 优化
computed
const filtered = computed(() =>
  list.filter(fn)
)
自动缓存,依赖不变不重算
缓存计算
useMemo
const filtered = useMemo(() =>
  list.filter(fn)
, [list])
手动声明依赖数组
shallowRef / shallowReactive
const state = shallowRef(bigObj)
// 仅追踪 .value 变化
// 不深层追踪内部属性
浅层响应
React.memo
const MyComp = React.memo(
  function MyComp(props) {
    // props 不变则跳过渲染
  }
)
v-once / v-memo
<!-- 仅渲染一次 -->
<div v-once>{{ msg }}</div>
<!-- 条件缓存 -->
<div v-memo="[item.id]">
渲染控制
useCallback
const handler = useCallback(
  () => doSomething(id),
  [id]
)
// 稳定函数引用
// 避免 child 不必要的 re-render
KeepAlive
<KeepAlive :include="['CompA']">
  <component :is="current" />
</KeepAlive>
组件缓存
手动实现 / 第三方
// React 无内置 KeepAlive
// 使用 display:none 切换
// 或使用 react-activation 库
{showA && <CompA />}
{showB && <CompB />}
defineAsyncComponent
const Async = defineAsyncComponent(
  () => import('./Heavy.vue')
)
懒加载
React.lazy + Suspense
const Lazy = React.lazy(
  () => import('./Heavy')
)
<Suspense fallback={<Loading />}>
  <Lazy />
</Suspense>
08 选型决策树
需要 React Native 移动端开发?
选 React(代码共享 + 原生渲染)
团队 HTML/CSS 背景为主?
选 Vue(模板语法更直观)
需要快速原型 / 中小型项目?
选 Vue(开箱即用,配置少)
大型团队 / 需要最大灵活性?
选 React(生态丰富,社区大)
国内企业 / 中文生态优先?
选 Vue(国内社区活跃,Element 生态完善)
需要 SSR 且优先考虑生态成熟度?
选 React + Next.js
TypeScript 深度集成需求?
选 Vue 3(TS 支持一流)
现有项目已使用某个框架?
保持一致,不轻易切换

选 Vue 的理由

+ 学习曲线平缓,上手快
+ 模板语法直观,关注点分离
+ 中文文档完善,国内生态丰富
+ 响应式自动化,开发体验好
+ SFC 单文件组件,结构清晰
+ Vite 官方推荐,构建极速
+ Pinia 轻量优雅,TS 支持好

选 React 的理由

+ 社区最大,解决方案最丰富
+ React Native 共享移动端开发
+ 函数式编程思想,灵活性高
+ Next.js SSR 方案最成熟
+ 大厂广泛使用(Meta、Netflix)
+ 人才市场更大
+ JSX 赋予完全的 JS 能力
09 面试高频对比题 (15 道)
使用提示:点击题目展开答案。面试时建议先说核心差异,再结合场景举例,最后总结各自优劣。
1 Vue 和 React 的核心区别是什么?
核心区别在响应式模型和视图表达:
1. 响应式:Vue 使用 Proxy 自动追踪依赖,修改数据自动更新视图;React 使用 setState 手动触发更新,通过 Virtual DOM Diff 计算最小变更。
2. 视图:Vue 使用模板 + 指令(v-if/v-for),关注点分离;React 使用 JSX,在 JS 中写 UI,一切皆 JavaScript。
3. 定位:Vue 是渐进式框架,官方维护全家桶;React 是 UI 库,生态由社区驱动。
4. 组件格式:Vue 是 SFC 单文件组件;React 是函数组件 + Hooks。
2 Vue 的响应式原理是什么?和 React 有什么不同?
Vue 3:使用 ES6 Proxy 对对象进行拦截。读取属性时自动收集依赖(track),修改属性时自动触发更新(trigger)。开发者只需修改数据,无需手动触发。

React:使用不可变数据 + setState。调用 setState 后触发整组件 re-render,再通过 Virtual DOM Diff 算出最小 DOM 操作。开发者必须显式调用 setter。

面试加分:Vue 的细粒度更新(组件级 → 模板级)vs React 的组件级更新(需要 memo/useMemo 优化)。
3 为什么选 Vue 不选 React?(或反过来)
选 Vue:团队偏 HTML/CSS 背景、需要快速上手、国内项目(Element 生态)、中小型项目、渐进式引入。

选 React:大型团队协作、需要 React Native、追求最大灵活性、已有 React 技术栈、国际化项目。

面试建议:不要拉踩,强调场景适配。两个框架都能做好的产品。
4 Vue 的 computed 和 React 的 useMemo 有什么区别?
功能相同:都是缓存计算结果,依赖不变时不重新计算。

区别:
1. Vue 的 computed 自动追踪依赖,无需手动声明;React 的 useMemo 需要手动传入依赖数组。
2. Vue 的 computed 有 getter/setter 完整写法;React 的 useMemo 只有 getter。
3. Vue computed 默认就是缓存的;React useMemo 在某些情况下可能被 React 丢弃(React 文档说明不保证缓存)。
5 Vue 的 v-model 和 React 的受控组件有什么区别?
Vue v-model:语法糖,自动绑定 value + 监听 input 事件。支持修饰符(.number, .trim, .lazy)。在自定义组件上可实现双向绑定。

React 受控组件:必须手动绑定 valueonChange。每个输入框都需要一个 state + setter,代码更冗长但更可控。

核心差异:Vue 的 v-model 是声明式双向绑定;React 强调单向数据流,"双向"通过回调手动实现。
6 Vue 和 React 的 Diff 算法有什么区别?
相同点:都使用 Virtual DOM + 同层比较策略,只比较同一层级。

不同点:
1. Vue 的 Diff 能精确知道哪些组件依赖了哪些数据(得益于响应式追踪),可以更精确地触发更新。
2. React 默认从触发 setState 的组件开始整棵子树 re-render,需要手动 memo 优化。
3. Vue 使用最长递增子序列算法优化列表 Diff;React 使用 key 匹配 + 位置移动。
4. Vue 3 引入 Block Tree + PatchFlag 静态标记,进一步优化模板编译产物。
7 Vue 的 Provide/Inject 和 React 的 Context 有什么区别?
功能相同:都是实现跨层级组件通信(祖孙传递),避免 props 层层透传。

区别:
1. Vue 使用 provide(key, value) + inject(key),API 更简洁;React 使用 createContext + Provider 包裹 + useContext
2. Vue 的 inject 支持默认值和工厂函数;React 使用 Provider 的 value 或 createContext 的默认值。
3. React Context 变更会导致所有消费者 re-render(除非 memo);Vue 的 provide/inject 也能精确响应式。
8 Vue 3 Composition API 和 React Hooks 有什么区别?
语法相似但底层完全不同:
1. 执行时机:Vue 的 setup() 只执行一次;React 的函数组件每次渲染都会重新执行。
2. 闭包陷阱:React Hooks 有闭包过期问题(stale closure),需要 useCallback/useRef 规避;Vue 的响应式引用始终是最新值。
3. 依赖数组:React useEffect 必须手动声明依赖;Vue watch/watchEffect 自动追踪依赖。
4. 调用顺序:React Hooks 依赖调用顺序(不能用在条件中);Vue 组合式函数无此限制。
5. 心智模型:Vue 是"响应式引用",React 是"快照式状态"。
9 Vue 和 React 在性能优化上的思路有什么不同?
Vue(默认优化好):
- Proxy 精确追踪 → 自动细粒度更新
- computed 自动缓存
- 模板编译时静态标记(PatchFlag)
- 开发者较少需要手动优化

React(需手动优化):
- setState → 整组件 re-render → Diff
- React.memo 避免子组件不必要的渲染
- useMemo 缓存计算结果
- useCallback 稳定函数引用
- 虚拟列表(react-window)处理大数据

面试总结:Vue 重编译时优化,React 重运行时优化。
10 Vue 和 React 的组件通信方式有哪些?
Vue
① Props / Emit
② Provide / Inject
③ Pinia / Vuex
④ EventBus (mitt)
⑤ $refs
React
① Props / Callback
② Context
③ Redux / Zustand
④ 状态提升
⑤ useRef

面试关键:React 没有 EventBus 概念(不推荐),通常用状态提升或全局状态管理替代。
11 Vue 的 template 和 React 的 JSX 各有什么优缺点?
Template(Vue):
+ 声明式,直观易读,设计师友好
+ 编译时优化(静态提升、PatchFlag)
+ 关注点分离(HTML/CSS/JS)
- 灵活性不如 JSX
- 复杂逻辑需要 filters / computed 辅助

JSX(React):
+ 完整的 JS 能力,灵活度高
+ 一个语言搞定 UI 和逻辑
+ TypeScript 类型推导更好
- 学习曲线较陡
- 编译时优化空间有限
12 Vue 的 nextTick 和 React 的 useEffect 执行时机有什么区别?
Vue nextTick:在 DOM 更新完成后执行回调。基于微任务(Promise.then)。用于在数据修改后获取更新后的 DOM。

React useEffect:在渲染提交到屏幕之后异步执行。也是微任务,但会批量处理。return 的 cleanup 在下一次 effect 执行前和卸载时执行。

关键差异:nextTick 是一次性的(Promise 风格),useEffect 是声明式的(每次依赖变化都执行)。
13 Pinia 和 Redux Toolkit 有什么区别?
Pinia(Vue):
- 基于 Composition API,defineStore 定义
- 无 mutations,直接修改 state
- 天然 TypeScript 支持
- 支持多个 store,可互相引用
- 极轻量,Vue 官方推荐

Redux Toolkit(React):
- 基于 createSlice + reducers
- 使用 Immer 实现不可变更新
- 单一 store,normalize 数据
- 中间件生态丰富(redux-saga, redux-observable)
- 标准化但学习成本较高
14 Vue 的 KeepAlive 和 React 的组件缓存有什么区别?
Vue KeepAlive:内置组件,使用 <KeepAlive> 包裹动态组件即可缓存。支持 include/exclude/max 属性精细控制。被缓存的组件触发 activated/deactivated 生命周期。

React:没有内置的 KeepAlive。常见方案:
1. CSS 隐藏(display:none),组件仍在 DOM 中
2. 使用第三方库 react-activation
3. 状态提升到父组件或全局 store

面试亮点:Vue 的 KeepAlive 是框架级支持,React 需要开发者自行解决或依赖社区方案。
15 谈谈你对"Vue 重编译时优化,React 重运行时优化"的理解?
Vue — 编译时优化:
Vue 的模板编译器在构建阶段就能做大量优化:
1. 静态提升:不变的节点只创建一次
2. PatchFlag:标记动态节点,Diff 时只比较标记部分
3. Block Tree:将模板按结构分块,缩小 Diff 范围
4. 内联事件缓存:避免重复创建事件处理器

React — 运行时优化:
React 的 JSX 无法在编译时做太多假设(JS 的动态性),优化主要在运行时:
1. Fiber 架构:可中断渲染,优先级调度
2. 批量更新:自动合并多次 setState
3. Concurrent Mode:并发渲染,time-slicing
4. 开发者手动优化:memo/useMemo/useCallback

总结:Vue 利用模板的约束性在编译阶段做优化;React 因为 JSX 的灵活性选择在运行时用 Fiber 架构做调度优化。两种策略各有优劣。
Vue vs React 对照手册 — 面试备战专用 | 最后更新:2026年6月