React轉(zhuǎn)Vue3的避坑指南
一、前言
從 React 轉(zhuǎn) Vue3,相信很多前端工程師都有過這個(gè)經(jīng)歷。兩者雖然都致力于"構(gòu)建用戶界面",但設(shè)計(jì)思想、API 風(fēng)格、狀態(tài)管理機(jī)制都有本質(zhì)差異。本文專門針對(duì) React 開發(fā)者視角,對(duì)照講解 Vue3 的核心概念,幫助你快速建立 Vue3 思維模型,少走彎路。
本文重點(diǎn)覆蓋:響應(yīng)式系統(tǒng)對(duì)比、組件寫法差異、Hooks 與 Composition API 的對(duì)照、狀態(tài)管理方案、以及常見思維誤區(qū)和正確做法。每個(gè)知識(shí)點(diǎn)都配有真實(shí)可運(yùn)行的代碼示例。
二、響應(yīng)式系統(tǒng):核心差異
2.1 React 的響應(yīng)式模型
React 使用不可變數(shù)據(jù)驅(qū)動(dòng)視圖更新。當(dāng) state 變化時(shí),React 會(huì)觸發(fā)重新渲染整棵組件樹(通過 Virtual DOM diff 優(yōu)化)。核心代碼如下:
// React 組件:狀態(tài)驅(qū)動(dòng)
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); // 觸發(fā)重新渲染
setLoading(false);
});
}, [userId]);
// React 的思維:數(shù)據(jù)變 -> 重新渲染 -> 對(duì)比虛擬 DOM -> 最小化更新
return loading ? <Spinner /> : <div>{user.name}</div>;
}
關(guān)鍵點(diǎn):React 開發(fā)者習(xí)慣"數(shù)據(jù)在前,視圖在后"——先有 state,再推導(dǎo) UI。狀態(tài)變化后 React 會(huì)自動(dòng)對(duì)比新舊 Virtual DOM,決定哪些真實(shí) DOM 需要更新。
2.2 Vue3 的響應(yīng)式模型
Vue3 使用Proxy 代理直接追蹤數(shù)據(jù)變化,數(shù)據(jù)在哪被用到,Vue3 就知道要更新哪里,不需要手動(dòng)觸發(fā)。
// Vue3 組件:聲明式模板 + 響應(yīng)式數(shù)據(jù)
import { ref, computed, onMounted } from 'vue';
defineComponent({
setup() {
// ref 包裝響應(yīng)式數(shù)據(jù)
const user = ref(null);
const loading = ref(true);
const userId = ref(1);
// 計(jì)算屬性
const displayName = computed(() => user.value?.name ?? '加載中');
onMounted(async () => {
const data = await fetchUser(userId.value);
user.value = data; // 直接賦值,Vue3 自動(dòng)追蹤
loading.value = false;
});
return { user, loading, displayName };
}
});<!-- Vue3 模板:直接使用響應(yīng)式數(shù)據(jù) -->
<template>
<div>{{ loading ? '加載中' : displayName }}</div>
</template>
2.3 兩者的核心差異對(duì)比
| 維度 | React | Vue3 |
|---|---|---|
| 響應(yīng)式實(shí)現(xiàn) | 不可變 state + Virtual DOM diff | Proxy 直接代理 + 依賴追蹤 |
| 更新觸發(fā) | 手動(dòng) setState / useState | 賦值即更新 |
| 渲染粒度 | 組件級(jí),需要 React.memo 優(yōu)化 | 精確到響應(yīng)式依賴的 DOM 節(jié)點(diǎn) |
| 學(xué)習(xí)曲線 | 思維簡(jiǎn)單,但性能優(yōu)化要主動(dòng) | 上手容易,性能優(yōu)化由框架兜底 |
三、組件寫法對(duì)照
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 生命周期對(duì)比
// React useEffect 替代所有生命周期
useEffect(() => {
// mounted
console.log('組件掛載');
return () => {
// unmounted cleanup
console.log('組件卸載清理');
};
}, []); // 空依賴 = didMount + willUnmount
useEffect(() => {
// didUpdate — 當(dāng) userId 變化時(shí)執(zhí)行
fetchUser(userId);
}, [userId]);
// Vue3 組合式 API 生命周期鉤子
import { onMounted, onUnmounted, watch } from 'vue';
onMounted(() => {
console.log('setup 執(zhí)行 = React 的 constructor');
});
watch(userId, (newId) => {
fetchUser(newId); // 等價(jià)于 React 的 useEffect(() => {...}, [userId])
});
onUnmounted(() => {
console.log('組件卸載 = React 的 componentWillUnmount');
});
四、Hooks vs Composition API:逐個(gè)對(duì)照
這是兩者最大的差異所在,也是 React 開發(fā)者遷移 Vue3 時(shí)最需要調(diào)整思維的地方。
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); // 基礎(chǔ)類型用 ref
const user = reactive({ name: '', age: 0 }); // 對(duì)象/數(shù)組用 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 精確監(jiān)聽
watch(() => user.name, (newName) => {
document.title = `${newName} 的主頁`;
});
// watchEffect 自動(dòng)收集依賴(類似 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(自定義組合式函數(shù))
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) };
}
五、常見思維誤區(qū)
誤區(qū)一:用 React 的不可變思維操作 Vue3 響應(yīng)式數(shù)據(jù)
React 開發(fā)者習(xí)慣了 setState({ ...state, key: newVal })(展開合并),在 Vue3 中對(duì) reactive 對(duì)象這樣做會(huì)丟失響應(yīng)式:
// 錯(cuò)誤 ? — 展開后變成普通對(duì)象,失去響應(yīng)式
user.value = { ...user.value, name: 'new name' };
// 正確 ? — 直接修改屬性
user.value.name = 'new name';
// 或者用 Object.assign(對(duì) ref)
Object.assign(user.value, { name: 'new name' });
誤區(qū)二:把 useEffect 的依賴當(dāng)成 watchEffect 的寫法
useEffect 依賴數(shù)組是"被動(dòng)觸發(fā)",watchEffect 自動(dòng)收集依賴但會(huì)立即執(zhí)行一次($ 有 lazy: true 選項(xiàng)關(guān)閉)。$ 中不要在 watchEffect 里直接修改被監(jiān)聽的對(duì)象,否則會(huì)導(dǎo)致死循環(huán)。
誤區(qū)三:忘了 .value
ref 包裝的變量在 script 中是引用,必須用 .value 讀寫,但在 <template> 中不需要——Vue3 自動(dòng)解包。
六、狀態(tài)管理方案對(duì)比
| 場(chǎng)景 | React 推薦方案 | Vue3 推薦方案 |
|---|---|---|
| 組件本地狀態(tài) | useState | ref / reactive |
| 跨組件共享 | Context + useReducer / Zustand | provide/inject + Pinia |
| 服務(wù)端數(shù)據(jù) | React Query / SWR | Pinia + REST / GraphQL |
| 全局配置 | Context | Pinia store |
Pinia vs Redux
// React Redux 風(fēng)格
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);
}
}
});
七、總結(jié):遷移 checklist
| 檢查項(xiàng) | React 思維 | Vue3 正確姿勢(shì) |
|---|---|---|
| 狀態(tài)更新 | setState(obj) | ref.value = xxx 或 reactiveObj.key = xxx |
| 對(duì)象更新 | {...obj, key: val} | 直接賦值或 Object.assign |
| 副作用 | useEffect(() => {...}, [dep]) | watch(() => dep, fn) 或 watchEffect(fn) |
| 計(jì)算屬性 | useMemo(fn, [dep]) | computed(fn) |
| 組件傳值 | props + callbacks | props + defineEmits |
| 全局狀態(tài) | Context / Redux / Zustand | Pinia |
| 清理邏輯 | return () => {...} | onUnmounted(() => {...}) |
到此這篇關(guān)于React轉(zhuǎn)Vue3的避坑指南的文章就介紹到這了,更多相關(guān)React轉(zhuǎn)Vue3避坑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何用webpack4從零開始構(gòu)建react開發(fā)環(huán)境
這篇文章主要介紹了詳解如何用webpack4從零開始構(gòu)建react開發(fā)環(huán)境,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01
React報(bào)錯(cuò)Element type is invalid解決案例
這篇文章主要為大家介紹了React報(bào)錯(cuò)Element type is invalid解決案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
React項(xiàng)目配置axios和反向代理和process.env環(huán)境配置等問題
這篇文章主要介紹了React項(xiàng)目配置axios和反向代理和process.env環(huán)境配置等問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
react路由守衛(wèi)的實(shí)現(xiàn)(路由攔截)
react不同于vue,通過在路由里設(shè)置meta元字符實(shí)現(xiàn)路由攔截。本文就詳細(xì)的介紹一下,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
React?中使用?Redux?Toolkit?狀態(tài)管理的實(shí)踐
本文主要介紹了Redux的核心概念及Redux?Toolkit的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-09-09
React中異步數(shù)據(jù)更新不及時(shí)問題及解決
這篇文章主要介紹了React中異步數(shù)據(jù)更新不及時(shí)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
React+Spring實(shí)現(xiàn)跨域問題的完美解決方法
這篇文章主要介紹了React+Spring實(shí)現(xiàn)跨域問題的完美解決方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08

