React转Vue3的避坑指南
一、前言
从 React 转 Vue3,相信很多前端工程师都有过这个经历。两者虽然都致力于"构建用户界面",但设计思想、API 风格、状态管理机制都有本质差异。本文专门针对 React 开发者视角,对照讲解 Vue3 的核心概念,帮助你快速建立 Vue3 思维模型,少走弯路。
本文重点覆盖:响应式系统对比、组件写法差异、Hooks 与 Composition API 的对照、状态管理方案、以及常见思维误区和正确做法。每个知识点都配有真实可运行的代码示例。
二、响应式系统:核心差异
2.1 React 的响应式模型
React 使用不可变数据驱动视图更新。当 state 变化时,React 会触发重新渲染整棵组件树(通过 Virtual DOM diff 优化)。核心代码如下:
// React 组件:状态驱动
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data); // 触发重新渲染
setLoading(false);
});
}, [userId]);
// React 的思维:数据变 -> 重新渲染 -> 对比虚拟 DOM -> 最小化更新
return loading ? <Spinner /> : <div>{user.name}</div>;
}
关键点:React 开发者习惯"数据在前,视图在后"——先有 state,再推导 UI。状态变化后 React 会自动对比新旧 Virtual DOM,决定哪些真实 DOM 需要更新。
2.2 Vue3 的响应式模型
Vue3 使用Proxy 代理直接追踪数据变化,数据在哪被用到,Vue3 就知道要更新哪里,不需要手动触发。
// Vue3 组件:声明式模板 + 响应式数据
import { ref, computed, onMounted } from 'vue';
defineComponent({
setup() {
// ref 包装响应式数据
const user = ref(null);
const loading = ref(true);
const userId = ref(1);
// 计算属性
const displayName = computed(() => user.value?.name ?? '加载中');
onMounted(async () => {
const data = await fetchUser(userId.value);
user.value = data; // 直接赋值,Vue3 自动追踪
loading.value = false;
});
return { user, loading, displayName };
}
});<!-- Vue3 模板:直接使用响应式数据 -->
<template>
<div>{{ loading ? '加载中' : displayName }}</div>
</template>
2.3 两者的核心差异对比
| 维度 | React | Vue3 |
|---|---|---|
| 响应式实现 | 不可变 state + Virtual DOM diff | Proxy 直接代理 + 依赖追踪 |
| 更新触发 | 手动 setState / useState | 赋值即更新 |
| 渲染粒度 | 组件级,需要 React.memo 优化 | 精确到响应式依赖的 DOM 节点 |
| 学习曲线 | 思维简单,但性能优化要主动 | 上手容易,性能优化由框架兜底 |
三、组件写法对照
3.1 Props 传递与类型检查
// React Props 定义
interface UserCardProps {
name: string;
age: number;
avatar?: string;
onUpdate: (id: number, name: string) => void;
}
function UserCard({ name, age, avatar, onUpdate }: UserCardProps) {
return <div onClick={() => onUpdate(1, name)}>{name}, {age}</div>;
}
// Vue3 Props 定义(defineProps 配合 TS)
interface UserCardProps {
name: string;
age: number;
avatar?: string;
emit: (event: 'update', id: number, name: string) => void;
}
const props = defineProps<UserCardProps>();
const emit = defineEmits<{
update: [id: number, name: string];
}>();
// Vue3 使用 emit
const handleClick = () => emit('update', 1, props.name);
3.2 生命周期对比
// React useEffect 替代所有生命周期
useEffect(() => {
// mounted
console.log('组件挂载');
return () => {
// unmounted cleanup
console.log('组件卸载清理');
};
}, []); // 空依赖 = didMount + willUnmount
useEffect(() => {
// didUpdate — 当 userId 变化时执行
fetchUser(userId);
}, [userId]);
// Vue3 组合式 API 生命周期钩子
import { onMounted, onUnmounted, watch } from 'vue';
onMounted(() => {
console.log('setup 执行 = React 的 constructor');
});
watch(userId, (newId) => {
fetchUser(newId); // 等价于 React 的 useEffect(() => {...}, [userId])
});
onUnmounted(() => {
console.log('组件卸载 = React 的 componentWillUnmount');
});
四、Hooks vs Composition API:逐个对照
这是两者最大的差异所在,也是 React 开发者迁移 Vue3 时最需要调整思维的地方。
4.1 useState → ref / reactive
// React
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
setCount(count + 1);
setUser({ ...user, name: 'new name' });
// Vue3
const count = ref(0); // 基础类型用 ref
const user = reactive({ name: '', age: 0 }); // 对象/数组用 reactive
count.value++; // ref 要 .value
user.name = 'new name'; // reactive 直接改
4.2 useEffect → watch / watchEffect
// React:副作用在 useEffect 里
useEffect(() => {
document.title = `${user.name} 的主页`;
}, [user.name]);
// Vue3:watch 精确监听
watch(() => user.name, (newName) => {
document.title = `${newName} 的主页`;
});
// watchEffect 自动收集依赖(类似 useEffect,但更直接)
watchEffect(() => {
document.title = `${user.name} 的主页`;
});
4.3 useMemo / useCallback → computed / readonly
// React const sortedList = useMemo(() => list.filter(x => x.active).sort((a, b) => a.name.localeCompare(b.name)), [list] ); const handleSubmit = useCallback((data) => submit(data), [submit]);
// Vue3 const sortedList = computed(() => list.filter(x => x.active).sort((a, b) => a.name.localeCompare(b.name)) ); const handleSubmit = (data: FormData) => submit(data); // Vue3 不需要 useCallback
4.4 自定义 Hooks → Composables
// React 自定义 Hook
function useUserSearch(keyword) {
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
search(keyword).then(data => {
setResults(data);
setLoading(false);
});
}, [keyword]);
return { results, loading };
}
// Vue3 Composables(自定义组合式函数)
function useUserSearch(keyword: Ref<string>) {
const results = ref([]);
const loading = ref(false);
watch(keyword, async (kw) => {
if (!kw) { results.value = []; return; }
loading.value = true;
results.value = await search(kw);
loading.value = false;
}, { immediate: true });
return { results: readonly(results), loading: readonly(loading) };
}
五、常见思维误区
误区一:用 React 的不可变思维操作 Vue3 响应式数据
React 开发者习惯了 setState({ ...state, key: newVal })(展开合并),在 Vue3 中对 reactive 对象这样做会丢失响应式:
// 错误 ❌ — 展开后变成普通对象,失去响应式
user.value = { ...user.value, name: 'new name' };
// 正确 ✅ — 直接修改属性
user.value.name = 'new name';
// 或者用 Object.assign(对 ref)
Object.assign(user.value, { name: 'new name' });
误区二:把 useEffect 的依赖当成 watchEffect 的写法
useEffect 依赖数组是"被动触发",watchEffect 自动收集依赖但会立即执行一次(${b} 有 lazy: true 选项关闭)。${b} 中不要在 watchEffect 里直接修改被监听的对象,否则会导致死循环。
误区三:忘了 .value
ref 包装的变量在 script 中是引用,必须用 .value 读写,但在 <template> 中不需要——Vue3 自动解包。
六、状态管理方案对比
| 场景 | React 推荐方案 | Vue3 推荐方案 |
|---|---|---|
| 组件本地状态 | useState | ref / reactive |
| 跨组件共享 | Context + useReducer / Zustand | provide/inject + Pinia |
| 服务端数据 | React Query / SWR | Pinia + REST / GraphQL |
| 全局配置 | Context | Pinia store |
Pinia vs Redux
// React Redux 风格
const userSlice = createSlice({
name: 'user',
initialState: { name: '', token: '' },
reducers: {
setUser: (state, action) => { state.name = action.payload.name; }
}
});
// Vue3 Pinia — 更直观,不需要 action/reducer 分离
export const useUserStore = defineStore('user', {
state: () => ({ name: '', token: '' }),
actions: {
setUser(data) {
this.name = data.name;
this.token = data.token;
},
async fetchUser(id) {
const data = await api.getUser(id);
this.setUser(data);
}
}
});
七、总结:迁移 checklist
| 检查项 | React 思维 | Vue3 正确姿势 |
|---|---|---|
| 状态更新 | setState(obj) | ref.value = xxx 或 reactiveObj.key = xxx |
| 对象更新 | {...obj, key: val} | 直接赋值或 Object.assign |
| 副作用 | useEffect(() => {...}, [dep]) | watch(() => dep, fn) 或 watchEffect(fn) |
| 计算属性 | useMemo(fn, [dep]) | computed(fn) |
| 组件传值 | props + callbacks | props + defineEmits |
| 全局状态 | Context / Redux / Zustand | Pinia |
| 清理逻辑 | return () => {...} | onUnmounted(() => {...}) |
到此这篇关于React转Vue3的避坑指南的文章就介绍到这了,更多相关React转Vue3避坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
React中使用useState时状态更新不生效的原因及解决方法
在使用 React 的 useState 钩子时,有时我们会遇到通过 set 方法更新状态后界面没有相应变化的情况,这可能是由于一些常见的问题导致的,本文将详细分析这些可能的原因,并提供相应的解决方案,需要的朋友可以参考下2025-12-12
深入理解React Native原生模块与JS模块通信的几种方式
本篇文章主要介绍了深入理解React Native原生模块与JS模块通信的几种方式,具有一定的参考价值,有兴趣的可以了解一下2017-07-07


最新评论