Composition API 与 Hooks 对比

Vue 3 Composition API 和 React Hooks 的异同点分析

问题

对比 Vue 3 的 Composition API 和 React Hooks,分析它们的设计理念、使用方式和差异。

解答

基本使用对比

React Hooks

import { useState, useEffect, useMemo, useCallback } from 'react';

function Counter() {
  // 状态声明
  const [count, setCount] = useState(0);
  const [name, setName] = useState('React');

  // 副作用
  useEffect(() => {
    document.title = `Count: ${count}`;
    
    // 清理函数
    return () => {
      console.log('cleanup');
    };
  }, [count]); // 依赖数组

  // 计算值(需要手动声明依赖)
  const doubled = useMemo(() => count * 2, [count]);

  // 缓存函数
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <p>{name}: {count} (doubled: {doubled})</p>
      <button onClick={increment}>+1</button>
    </div>
  );
}

Vue Composition API

<script setup>
import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue';

// 状态声明
const count = ref(0);
const name = ref('Vue');

// 或使用 reactive
const state = reactive({
  count: 0,
  name: 'Vue'
});

// 副作用
watch(count, (newVal, oldVal) => {
  document.title = `Count: ${newVal}`;
});

// 生命周期
onMounted(() => {
  console.log('mounted');
});

onUnmounted(() => {
  console.log('cleanup');
});

// 计算值(自动追踪依赖)
const doubled = computed(() => count.value * 2);

// 普通函数,无需缓存
const increment = () => {
  count.value++;
};
</script>

<template>
  <div>
    <p>{{ name }}: {{ count }} (doubled: {{ doubled }})</p>
    <button @click="increment">+1</button>
  </div>
</template>

响应式原理对比

// React: 不可变数据 + 重新执行组件函数
function ReactComponent() {
  const [list, setList] = useState([1, 2, 3]);
  
  const addItem = () => {
    // 必须创建新数组
    setList([...list, 4]);
    // 错误:list.push(4) 不会触发更新
  };
  
  // 每次状态变化,整个函数重新执行
  console.log('render');
  
  return <div>{list.join(',')}</div>;
}

// Vue: 可变数据 + Proxy 代理
const list = ref([1, 2, 3]);

const addItem = () => {
  // 直接修改即可
  list.value.push(4);
};

// setup 只执行一次,通过 Proxy 追踪变化

依赖追踪对比

// React: 手动声明依赖
function ReactExample() {
  const [a, setA] = useState(1);
  const [b, setB] = useState(2);
  
  // 必须手动列出依赖,遗漏会导致 bug
  const sum = useMemo(() => a + b, [a, b]);
  
  useEffect(() => {
    console.log(a, b);
  }, [a, b]); // 忘记加 b 会导致闭包陷阱
  
  return <div>{sum}</div>;
}

// Vue: 自动追踪依赖
const a = ref(1);
const b = ref(2);

// 自动追踪,无需手动声明
const sum = computed(() => a.value + b.value);

watch([a, b], ([newA, newB]) => {
  console.log(newA, newB);
});

// 或使用 watchEffect 自动收集
watchEffect(() => {
  console.log(a.value, b.value); // 自动追踪 a 和 b
});

调用限制对比

// React Hooks 有严格的调用规则
function ReactComponent() {
  // ❌ 错误:不能在条件语句中使用
  if (condition) {
    const [state, setState] = useState(0);
  }
  
  // ❌ 错误:不能在循环中使用
  for (let i = 0; i < 3; i++) {
    useEffect(() => {});
  }
  
  // ❌ 错误:不能在普通函数中使用
  function helper() {
    const [x, setX] = useState(0);
  }
  
  // ✅ 正确:只能在组件顶层使用
  const [count, setCount] = useState(0);
}

// Vue Composition API 更灵活
const count = ref(0);

// ✅ 可以在条件语句中使用
if (condition) {
  const extra = ref('extra');
}

// ✅ 可以在任何地方调用
function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;
  return { count, increment };
}

// ✅ 可以在 setup 外部定义和使用
const { count, increment } = useCounter();

逻辑复用对比

// React: 自定义 Hook
function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handler = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);
  
  return position;
}

// 使用
function Component() {
  const { x, y } = useMousePosition();
  return <div>{x}, {y}</div>;
}

// Vue: 组合式函数
function useMousePosition() {
  const x = ref(0);
  const y = ref(0);
  
  const handler = (e) => {
    x.value = e.clientX;
    y.value = e.clientY;
  };
  
  onMounted(() => window.addEventListener('mousemove', handler));
  onUnmounted(() => window.removeEventListener('mousemove', handler));
  
  return { x, y };
}

// 使用
const { x, y } = useMousePosition();

主要差异总结

特性React HooksVue Composition API
响应式不可变数据,setState 触发Proxy 代理,自动追踪
执行时机每次渲染都执行setup 只执行一次
依赖追踪手动声明依赖数组自动收集依赖
调用限制只能在顶层调用无位置限制
函数缓存需要 useCallback不需要,函数引用稳定
闭包问题容易产生过期闭包通过 .value 访问最新值

关键点

  • 响应式机制不同:React 基于不可变数据和重渲染,Vue 基于 Proxy 代理和依赖追踪
  • 执行模式不同:React 组件函数每次渲染都执行,Vue 的 setup 只执行一次
  • 依赖管理不同:React 需要手动声明依赖数组,Vue 自动追踪依赖
  • 调用限制不同:React Hooks 必须在组件顶层调用,Vue 没有此限制
  • 心智模型不同:React 偏函数式(纯函数 + 不可变),Vue 偏响应式(可变数据 + 自动更新)