Vue Composition API 与 React Hooks 深度对比

Vue Composition API 与 React Hooks 深度对比

虽然Vue 3的Composition API和React Hooks在表面上看起来相似(都是函数式API,都用于逻辑复用),但它们在设计哲学、工作原理和使用模式上有本质区别。下面从多个维度详细分析它们的异同。

1. 核心设计理念

Vue Composition API

  • 对响应式系统的自然演进:Composition API保留了Vue核心的响应式系统,是对Options API的增强而非颠覆
  • 解决代码组织问题:旨在将相关功能的代码组织在一起,而非按选项类型分散
  • 补充而非替代:可以与Options API在同一项目中混用

React Hooks

  • 函数组件的能力扩展:为了解决类组件的复杂性和生命周期方法中混杂不相关逻辑的问题
  • 完全的范式转变:从类组件到函数组件的彻底转变,Hooks是这一转变的核心
  • 函数式编程思想:更强调纯函数和不可变数据原则

2. 响应式机制与更新原理

Vue Composition API

1
2
3
4
5
6
7
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

// 自动依赖追踪,无需指定依赖数组
watchEffect(() => {
console.log(doubleCount.value) // 自动追踪count和doubleCount的依赖
})
  • 自动依赖追踪:基于Proxy的响应式系统自动分析依赖关系
  • 精确更新:只更新受影响的DOM部分,无需手动优化
  • 隐式订阅:模板自动订阅响应式数据变化

React Hooks

1
2
3
4
5
6
7
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => count * 2, [count]);

// 必须手动指定依赖数组
useEffect(() => {
console.log(doubleCount);
}, [doubleCount]); // 依赖必须手动列出
  • 手动依赖管理:所有副作用和派生状态必须显式指定依赖
  • 组件级重渲染:状态变化触发整个组件函数重新执行
  • 需要手动优化:通过React.memo、useMemo、useCallback等减少不必要的渲染

3. 调用规则与使用约束

Vue Composition API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 可以在条件语句中调用
if (shouldTrack) {
watch(someRef, callback)
}

// 可以在循环中调用
items.value.forEach(item => {
const data = ref(item)
// ...
})

// 可以在嵌套函数中调用
function setupData() {
const state = reactive({ count: 0 })
return state
}
  • 几乎没有调用限制:可以在任何JavaScript上下文中调用Composition API
  • 无调用顺序要求:可以自由地在条件、循环中使用
  • 不需要特定前缀:API名称不强制特定前缀

React Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不能在条件语句中调用(违反Rules of Hooks)
if (condition) {
const [state, setState] = useState(initialState); // ❌ 错误
}

// 必须在顶层调用
function Component() {
const [state, setState] = useState(initialState); // ✅ 正确

// 不能在嵌套函数中调用内置Hooks
function handleClick() {
useEffect(() => {}); // ❌ 错误
}
}
  • 严格的Rules of Hooks
    1. 只在React函数组件或自定义Hook中调用
    2. 只在顶层调用,不在条件、循环或嵌套函数中调用
  • 调用顺序必须一致:每次渲染时Hooks的调用顺序必须相同
  • 命名约定:自定义Hook必须以”use”前缀命名

4. 状态访问与修改方式

Vue Composition API

1
2
3
4
5
6
7
const count = ref(0)
console.log(count.value) // 读取需要.value
count.value++ // 修改需要.value

const state = reactive({ count: 0 })
console.log(state.count) // 读取直接通过属性
state.count++ // 修改直接通过属性
  • 显式响应式包装:ref需要通过.value访问,提供明确的响应式边界
  • 深度响应式:reactive创建的对象及其嵌套属性都是响应式的
  • 自动解包:模板中自动解包ref,无需.value

React Hooks

1
2
3
4
5
6
7
const [count, setCount] = useState(0);
console.log(count); // 直接读取
setCount(count + 1); // 通过setter修改

const [state, setState] = useState({ count: 0 });
console.log(state.count); // 直接读取
setState(prev => ({ ...prev, count: prev.count + 1 })); // 不可变更新
  • 不可变数据原则:状态不可直接修改,必须通过setter创建新对象
  • 直接访问:状态值直接访问,没有包装层
  • 函数式更新:推荐使用函数式更新方式(setCount(prev => prev + 1))

5. 生命周期处理

Vue Composition API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
onMounted(() => {
// 组件挂载后执行
})

onUpdated(() => {
// 组件更新后执行
})

onUnmounted(() => {
// 组件卸载前执行
})

watch(source, (newVal, oldVal) => {
// 响应式数据变化时执行
}, options)
  • 显式的生命周期钩子:每个生命周期阶段有专门的钩子函数
  • 独立的watch API:专门用于响应式数据变化的监听
  • 清晰的执行时机:生命周期钩子对应明确的组件阶段

React Hooks

1
2
3
4
5
6
useEffect(() => {
// 相当于componentDidMount和componentDidUpdate
return () => {
// 相当于componentWillUnmount
};
}, [dependencies]); // 依赖数组决定何时执行
  • 统一的useEffect:一个Hook处理多种生命周期场景
  • 依赖数组决定行为
    • 空数组[]:只在挂载和卸载时执行(componentDidMount + componentWillUnmount)
    • 有依赖:当依赖变化时执行
    • 无依赖数组:每次渲染后都执行
  • 清理函数:返回的函数作为清理逻辑,在下次执行前或卸载时调用

6. 逻辑复用模式

Vue Composition API (Composables)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// useMouse.js
export function useMouse() {
const x = ref(0)
const y = ref(0)

function update(event) {
x.value = event.pageX
y.value = event.pageY
}

onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))

return { x, y }
}

// 使用
const { x, y } = useMouse()
  • 状态共享:多个composables可以共享同一个响应式状态
  • 无调用顺序限制:可以在任何地方调用composables
  • 隐式上下文:自动在当前活动的组件实例中工作
  • 自然组合:可以像普通函数一样组合多个composables

React Hooks (Custom Hooks)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// useMouse.js
export function useMouse() {
const [position, setPosition] = useState({ x: 0, y: 0 });

useEffect(() => {
const update = event => setPosition({ x: event.pageX, y: event.pageY });
window.addEventListener('mousemove', update);
return () => window.removeEventListener('mousemove', update);
}, []);

return position;
}

// 使用
const { x, y } = useMouse();
  • 状态隔离:每个Hook调用创建独立的状态,不自动共享
  • 严格调用规则:必须遵循Rules of Hooks
  • 显式依赖:必须手动管理依赖关系
  • 作用域限制:只能在函数组件或自定义Hook中调用

7. 性能优化方式

Vue Composition API

  • 自动优化:响应式系统自动追踪依赖,只更新受影响的部分
  • 细粒度更新:模板编译器生成精确的更新函数
  • 显式优化APIshallowRefmarkRaw等用于特殊场景优化

React Hooks

  • 手动优化:需要开发者显式优化
  • 渲染优化
    • React.memo:防止不必要的子组件重渲染
    • useMemo:缓存计算结果
    • useCallback:缓存函数引用
  • 状态拆分:需要将状态拆分为多个useState以避免不必要的重渲染

8. TypeScript支持

Vue Composition API

1
2
3
4
5
6
7
8
9
10
11
12
const count = ref<number>(0)
const items = ref<string[]>([])

interface User {
id: number
name: string
}

const user = reactive<User>({
id: 1,
name: 'Vue'
})
  • 设计时考虑TypeScript:Composition API从设计之初就考虑了TS支持
  • 优秀的类型推断:大多数情况下无需显式类型注解
  • ref和reactive保持类型:保持原始类型的完整信息

React Hooks

1
2
3
4
5
6
7
8
9
const [count, setCount] = useState<number>(0);
const [items, setItems] = useState<string[]>([]);

interface User {
id: number;
name: string;
}

const [user, setUser] = useState<User>({ id: 1, name: '' });
  • 强大但有时需要显式类型:某些复杂场景需要手动提供类型
  • 泛型支持:通过泛型参数指定状态类型
  • 类型推断有时不够精确:特别是在自定义Hook中

9. 代码组织与可维护性

Vue Composition API

1
2
3
4
5
6
7
8
9
10
11
12
13
// 可以按功能组织代码
setup() {
// 用户相关逻辑
const { user, fetchUser } = useUser()

// 表单相关逻辑
const { form, validate, submit } = useForm()

// 数据获取逻辑
const { data, loading, error } = useDataFetcher('/api/data')

return { user, form, data, loading, error, validate, submit, fetchUser }
}
  • 按功能垂直切分:将相关逻辑组织在同一代码块
  • 更好的可读性:大型组件中更容易理解相关功能
  • 更少的嵌套:避免了React中常见的多层嵌套Provider

React Hooks

1
2
3
4
5
6
7
8
9
10
11
12
function Component() {
// 状态分散在多个Hooks调用中
const [user, fetchUser] = useUser();
const [formState, { validate, submit }] = useForm();
const { data, loading, error } = useDataFetcher('/api/data');

// 副作用分散在多个useEffect中
useEffect(() => { /* 处理user变化 */ }, [user]);
useEffect(() => { /* 处理表单验证 */ }, [formState]);

return (/* JSX */);
}
  • 按Hook类型水平组织:状态、副作用等分散在不同Hook调用
  • 条件逻辑复杂化:条件逻辑可能导致Hooks调用顺序变化
  • 嵌套的Context Providers:状态管理需要多层Provider嵌套

10. 本质区别总结

特性 Vue Composition API React Hooks
核心机制 响应式系统(数据驱动) 不可变数据+函数式更新
依赖追踪 自动(基于Proxy) 手动(依赖数组)
调用规则 无限制 严格Rules of Hooks
更新粒度 细粒度(精准更新) 组件级(需要手动优化)
状态访问 ref需要.value,reactive直接访问 直接访问,通过setter修改
生命周期 专用钩子(onMounted等) 统一useEffect+依赖数组
逻辑复用 Composables(无状态共享问题) Custom Hooks(状态隔离)
性能优化 框架自动优化为主 开发者手动优化为主
TS支持 优秀(设计时考虑) 良好(需要更多类型注解)
学习曲线 需要理解响应式概念 需要理解函数式和不可变原则

结语

Vue Composition API和React Hooks解决了相似的问题(逻辑复用、代码组织),但采用了完全不同的技术路径:

  • Vue Composition API 是对其响应式核心的自然演进,保留了Vue的声明式和响应式特性,同时提供了更灵活的代码组织方式。它更像一个增强版的Vue,学习曲线对于Vue开发者较为平缓。

  • React Hooks 代表了React从类组件到函数组件的范式转变,拥抱了函数式编程思想和不可变数据原则。它要求开发者以不同的思维方式构建应用,但提供了更简洁、更可预测的代码模式。

选择哪种模式更多取决于项目需求、团队熟悉度和应用复杂度,而非技术优劣。两者都有成熟的生态系统和最佳实践,都能构建出色的用户界面。


Vue Composition API 与 React Hooks 深度对比
https://zjw93615.github.io/2025/12/06/React_Vue/Vue Composition API 与 React Hooks 深度对比/
作者
嘉炜
发布于
2025年12月6日
许可协议