React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React转Vue3避坑

React转Vue3的避坑指南

作者:斌味代码

从 React 转 Vue3,相信很多前端工程师都有过这个经历,本文专门针对 React 开发者视角,对照讲解 Vue3 的核心概念,帮助你快速建立 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避坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文