news 2026/4/23 22:51:55

React 转 Vue3 完整踩坑记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 两者的核心差异对比

维度ReactVue3
响应式实现不可变 state + Virtual DOM diffProxy 直接代理 + 依赖追踪
更新触发手动 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 推荐方案
组件本地状态useStateref / reactive
跨组件共享Context + useReducer / Zustandprovide/inject + Pinia
服务端数据React Query / SWRPinia + REST / GraphQL
全局配置ContextPinia 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 + callbacksprops + defineEmits
全局状态Context / Redux / ZustandPinia
清理逻辑return () => {...}onUnmounted(() => {...})

💬收藏本文!关注我,后续更新更多 React → Vue3 实战系列文章。


💬觉得有用?点赞+收藏+关注!后续持续更新《框架迁移避坑》系列,React↔Vue3↔Angular 全覆盖。

标签:React | Vue3 | 迁移 | 实战 | 前端

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 22:49:48

从安防监控到网络直播:PS流封装H.264的实战配置与优化避坑指南

从安防监控到网络直播&#xff1a;PS流封装H.264的实战配置与优化避坑指南 在视频技术领域&#xff0c;PS流&#xff08;Program Stream&#xff09;作为一种经典的媒体封装格式&#xff0c;已经从传统的广播电视领域延伸到了安防监控和互联网直播等多个应用场景。特别是在安防…

作者头像 李华