一、前言:Vue3 Composition API 是 Vue 开发的新范式
如果你是 React 开发者,Vue3 的 Composition API 会让你感到既熟悉又陌生。本文从 React 开发者视角,逐一对照 Hooks 与 Composition API 的核心用法,帮你快速上手并避开常见坑。
二、核心概念对照
2.1 状态管理:useState vs ref/reactive
tsx
// React
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
setCount(prev => prev + 1);
setUser(prev => ({ ...prev, name: 'Tom' }));
ts
// Vue3
const count = ref(0);
const user = reactive({ name: '', age: 0 });
count.value++;
user.name = 'Tom'; // 直接赋值,不需要 setState
关键差异 :Vue3 的 reactive 对象可以直接修改属性,无需 immer 或 spread 语法。ref 基础类型需要 .value,在模板中自动解包。
2.2 副作用:useEffect vs watchEffect/watch
tsx
// React
useEffect(() => {
document.title = `${count} times clicked`;
return () => {
// 清理副作用
};
}, [count]);
ts
// Vue3
watchEffect(() => {
document.title = `${count.value} times clicked`;
// 返回清理函数
return () => { /* cleanup */ };
});
// 精确监听
watch(count, (newVal, oldVal) => {
console.log(`${oldVal} → ${newVal}`);
});
关键差异 :Vue3 的 watchEffect 自动追踪依赖,不需要手写依赖数组。watch 精确监听,类似 useEffect 指定 deps。
2.3 计算属性:useMemo vs computed
tsx
// React
const doubleCount = useMemo(() => count * 2, [count]);
ts
// Vue3
const doubleCount = computed(() => count.value * 2);
// doubleCount.value 是只读的,依赖变化时自动更新
关键差异 :Vue3 的 computed 天然缓存,不需要手动写 deps,访问 .value 即得最新值。
三、生命周期对照
| React Hook | Vue3 Composition | 时机 |
|---|---|---|
| 无(构造函数) | setup() 本身 |
组件初始化 |
useEffect(fn, []) |
onMounted(fn) |
DOM 挂载后 |
useEffect(() => fn, []) |
onUnmounted(fn) |
组件卸载 |
useEffect(fn, [dep]) |
watch/watchEffect |
依赖变化 |
| 无 | onUpdated(fn) |
每次更新后 |
ts
// Vue3 生命周期示例
import { onMounted, onUnmounted, onUpdated } from 'vue';
export function useEventListener(event: string, handler: Function) {
onMounted(() => window.addEventListener(event, handler as EventListener));
onUnmounted(() => window.removeEventListener(event, handler as EventListener));
}
四、组合式函数(类比 React 自定义 Hooks)
tsx
// React 自定义 Hook
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener('resize', handler);
handler();
return () => window.removeEventListener('resize', handler);
}, []);
return size;
}
ts
// Vue3 组合式函数(Composable)
export function useWindowSize() {
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);
const handler = () => {
width.value = window.innerWidth;
height.value = window.innerHeight;
};
onMounted(() => window.addEventListener('resize', handler));
onUnmounted(() => window.removeEventListener('resize', handler));
return { width, height };
}
// 使用
const { width, height } = useWindowSize();
五、常见坑点
坑1:解构 reactive 对象失去响应性
ts
// ❌ 错误:解构后 name 不再是响应式的
const { name } = reactive({ name: 'Tom' });
// ✅ 正确:用 toRefs 保持响应性
const state = reactive({ name: 'Tom', age: 18 });
const { name, age } = toRefs(state);
坑2:ref 在模板里不需要 .value,但在 JS 里需要
ts
const count = ref(0);
// 模板中:{{ count }} ← 自动解包,不用 .value
// JS中:count.value++ ← 必须 .value
坑3:watch 默认不立即执行
ts
// React useEffect 会在 mount 时立即执行
useEffect(() => { fetchData(); }, [userId]);
// Vue3 watch 默认不立即执行,需要加 immediate: true
watch(userId, fetchData, { immediate: true });
// 或者用 watchEffect(总是立即执行)
watchEffect(() => fetchData(userId.value));
六、总结:从 React 迁移的心智模型
- ref/reactive 替代 useState,reactive 对象可以直接修改,无需 setState
- watchEffect/watch 替代 useEffect,自动追踪依赖,无需 deps 数组
- computed 替代 useMemo,天然缓存,语法更简洁
- Composables 替代自定义 Hooks,结构和写法几乎一致
- 最大不同:Vue3 用 Proxy 追踪依赖,响应式是自动的;React 需要手动声明 deps
💬 关注我,React↔Vue3 迁移避坑系列持续更新,帮你平滑过渡!
💬 觉得有用?点赞+收藏+关注! 后续持续更新《框架迁移避坑》系列,React↔Vue3↔Angular 全覆盖。
标签:react | vue3 | compositionapi | 前端 | 对比 | 避坑