React vs Vue:源码深度对比

63 阅读18分钟

React vs Vue:源码深度对比

基于 React 19 和 Vue 3.5 源码深度分析,从设计哲学到实现细节,全方位对比两大框架的核心差异。一文搞懂为什么 React 选择不可变,Vue 选择响应式。

2024-2025 新特性速览

React 19 重大更新

// 1. Actions:异步状态管理的革命
function UpdateName() {
  const [error, submitAction, isPending] = useActionState(
    async (prevState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) return error;
      redirect("/profile");
    },
    null
  );

  return (
    <form action={submitAction}>
      <input name="name" />
      <button disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

// 2. useOptimistic:乐观更新
function LikeButton({ initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (state) => state + 1
  );

  async function handleLike() {
    addOptimisticLike();  // 🔥 立即显示 +1
    await api.like();      // 后台请求
    setLikes(l => l + 1);  // 真实更新
  }

  return <button onClick={handleLike}>👍 {optimisticLikes}</button>;
}

// 3. use():在组件中直接 await Promise
function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);  // 🔥 直接使用 Promise
  return comments.map(c => <Comment key={c.id} {...c} />);
}

// 4. ref 作为 prop 传递(不再需要 forwardRef)
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// 5. 文档元数据原生支持
function BlogPost({ post }) {
  return (
    <article>
      <title>{post.title}</title>  {/* 🔥 自动提升到 <head> */}
      <meta name="description" content={post.excerpt} />
      <h1>{post.title}</h1>
    </article>
  );
}

Vue 3.5 重大更新

// 1. 响应式性能大幅提升(内存减少 56%)
// 内部重构:从 Set 改为双向链表追踪依赖
// 大型应用内存占用显著降低

// 2. 响应式 Props 解构
const { count } = defineProps(['count']);
watchEffect(() => {
  console.log(count);  // 🔥 直接解构,保持响应式!
});

// 3. useTemplateRef:类型安全的模板引用
const inputRef = useTemplateRef<HTMLInputElement>('input');
// <input ref="input" />

// 4. useId:SSR 安全的唯一 ID
const id = useId();
// <label :for="id">Name</label>
// <input :id="id" />

// 5. Lazy Hydration(实验性)
import { defineAsyncComponent, hydrateOnVisible } from 'vue';

const HeavyComponent = defineAsyncComponent({
  loader: () => import('./Heavy.vue'),
  hydrate: hydrateOnVisible()  // 🔥 可见时才 hydrate
});

新特性对比

特性React 19Vue 3.5
异步状态useActionState无(用 composable)
乐观更新useOptimistic无(手动实现)
Promise 消费use()await(setup 中)
ref 传递原生 propdefineExpose
文档元数据原生支持useHead(插件)
响应式优化-内存减少 56%
Props 解构-保持响应式
懒加载 hydration-hydrateOnVisible

一、设计哲学:两种思维方式

1.1 核心公式

React:  UI = f(state)           → 状态的函数
Vue:    UI = render(reactive(state))  → 响应式渲染

1.2 一图看懂差异

┌─────────────────────────────────────────────────────────────────┐
│                     React 的世界观                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   "每次渲染都是一张新照片"                                        │
│                                                                  │
│   state = { count: 0 }                                          │
│        │                                                         │
│        ▼  setState({ count: 1 })                                │
│   ┌─────────────┐                                               │
│   │  重新执行    │  ← 整个组件函数重新运行                        │
│   │  组件函数    │  ← 创建新的 JSX                               │
│   └─────────────┘  ← Diff 找出变化                              │
│        │                                                         │
│        ▼                                                         │
│   更新 DOM                                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     Vue 的世界观                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   "我知道谁在用这个数据"                                          │
│                                                                  │
│   state = reactive({ count: 0 })                                │
│        │                                                         │
│        ▼  state.count = 1                                       │
│   ┌─────────────┐                                               │
│   │  Proxy 拦截 │  ← 知道 count 变了                            │
│   │  trigger()  │  ← 知道谁依赖 count                           │
│   └─────────────┘  ← 只更新相关部分                             │
│        │                                                         │
│        ▼                                                         │
│   精准更新 DOM                                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1.3 核心差异表

维度ReactVue
数据理念不可变(Immutable)可变(Mutable)
更新触发显式调用 setState自动追踪依赖
更新粒度组件级别属性级别
模板方案JSX(JS 中写 HTML)Template(HTML 中写 JS)
优化方式手动(useMemo/useCallback)自动(编译时优化)
心智模型函数式思维响应式思维

二、架构对比:三大模块 vs 三大系统

┌─────────────────────────────────────────────────────────────────┐
│                        React 架构                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│   │  Scheduler  │  │ Reconciler  │  │  Renderer   │             │
│   │   调度器     │  │   协调器     │  │   渲染器    │             │
│   │             │  │             │  │             │             │
│   │ • 优先级    │  │ • Fiber树   │  │ • ReactDOM  │             │
│   │ • 时间切片  │  │ • Diff算法  │  │ • Native    │             │
│   │ • 最小堆    │  │ • Hooks     │  │ • Three.js  │             │
│   └─────────────┘  └─────────────┘  └─────────────┘             │
│         │                │                │                      │
│         └────────────────┼────────────────┘                      │
│                          ▼                                       │
│              可中断渲染 + 优先级抢占                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        Vue 架构                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐             │
│   │  Compiler   │  │  Runtime    │  │ Reactivity  │             │
│   │   编译器     │  │   运行时     │  │   响应式    │             │
│   │             │  │             │  │             │             │
│   │ • Parse     │  │ • Renderer  │  │ • Proxy     │             │
│   │ • Transform │  │ • Scheduler │  │ • Dep/Effect│             │
│   │ • Codegen   │  │ • Component │  │ • Computed  │             │
│   └─────────────┘  └─────────────┘  └─────────────┘             │
│         │                │                │                      │
│         └────────────────┼────────────────┘                      │
│                          ▼                                       │
│              编译时优化 + 精准更新                                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

三、响应式原理:不可变 vs Proxy

3.1 React:不可变数据 + 调度更新

// React 状态更新的完整流程
const [count, setCount] = useState(0);

// 你写的代码
setCount(1);

// React 内部发生了什么?
function dispatchSetState(fiber, queue, action) {
  // 1. 创建更新对象
  const update = { action, next: null };
  
  // 2. 🔥 Eager State 优化:提前计算
  const currentState = queue.lastRenderedState;
  const eagerState = typeof action === 'function' 
    ? action(currentState) 
    : action;
  
  // 3. 🔥 相同值直接跳过!
  if (Object.is(eagerState, currentState)) {
    return;  // Bailout! 不触发更新
  }
  
  // 4. 入队更新
  enqueueConcurrentHookUpdate(fiber, queue, update);
  
  // 5. 调度更新
  scheduleUpdateOnFiber(fiber);
}

3.2 Vue:Proxy + 依赖追踪

// Vue 响应式的完整流程
const state = reactive({ count: 0 });

// 你写的代码
state.count = 1;

// Vue 内部发生了什么?
const mutableHandlers = {
  get(target, key) {
    // 1. 🔥 依赖收集
    track(target, key);  // 记录"谁在读这个属性"
    return Reflect.get(target, key);
  },
  
  set(target, key, value) {
    const oldValue = target[key];
    const result = Reflect.set(target, key, value);
    
    // 2. 🔥 触发更新
    if (hasChanged(value, oldValue)) {
      trigger(target, key);  // 通知"所有依赖这个属性的人"
    }
    return result;
  }
};

3.3 依赖收集对比图

┌─────────────────────────────────────────────────────────────────┐
│                    React:无依赖追踪                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   function Component() {                                        │
│     const [a, setA] = useState(0);                              │
│     const [b, setB] = useState(0);                              │
│                                                                  │
│     return <div>{a} + {b}</div>;                                │
│   }                                                              │
│                                                                  │
│   setA(1);  → 整个组件重新执行 → 重新计算 a + b                  │
│   setB(1);  → 整个组件重新执行 → 重新计算 a + b                  │
│                                                                  │
│   💡 React 不知道 div 只依赖 ab,所以每次都全量执行          │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue:精准依赖追踪                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   const a = ref(0);                                             │
│   const b = ref(0);                                             │
│                                                                  │
│   // 渲染时自动收集依赖                                          │
│   effect(() => {                                                │
│     render(a.value + b.value);                                  │
│   });                                                            │
│   // Vue 知道:这个 effect 依赖 ab                           │
│                                                                  │
│   a.value = 1;  → 只触发依赖 a 的 effect                        │
│   b.value = 1;  → 只触发依赖 b 的 effect                        │
│                                                                  │
│   💡 Vue 精确知道谁依赖谁,只更新必要的部分                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

3.4 响应式对比表

特性ReactVue
实现方式不可变数据 + setStateProxy + 依赖追踪
更新粒度组件级别属性级别
深层响应需要展开/immer自动深层响应
数组更新需要新数组直接 push/splice
性能优化手动 useMemo/useCallback自动,computed 缓存
心智负担需要理解闭包、依赖数组需要理解 .value

四、Hooks vs Composition API:殊途同归

4.1 代码对比

// ==================== React Hooks ====================
function useCounter() {
  const [count, setCount] = useState(0);
  const double = useMemo(() => count * 2, [count]);  // 手动声明依赖
  
  useEffect(() => {
    document.title = `Count: ${count}`;
    return () => console.log('cleanup');  // 清理函数
  }, [count]);  // 手动声明依赖
  
  return { count, double, increment: () => setCount(c => c + 1) };
}

// ==================== Vue Composition API ====================
function useCounter() {
  const count = ref(0);
  const double = computed(() => count.value * 2);  // 自动追踪依赖
  
  watchEffect((onCleanup) => {
    document.title = `Count: ${count.value}`;
    onCleanup(() => console.log('cleanup'));
  });  // 自动追踪依赖
  
  return { count, double, increment: () => count.value++ };
}

4.2 useEffect vs watch/watchEffect

┌─────────────────────────────────────────────────────────────────┐
│                    useEffect 执行时机                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   Render 阶段 ──► Commit 阶段 ──► 浏览器绘制 ──► useEffect      │
│       │              │              │              │             │
│       │              │              │              ▼             │
│   执行组件函数    DOM 操作      屏幕更新      异步执行回调        │
│                                                                  │
│   💡 useEffect 在浏览器绘制后异步执行,不阻塞渲染                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    watchEffect 执行时机                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   数据变化 ──► queueJob ──► flushJobs ──► watchEffect ──► DOM   │
│       │           │            │              │            │     │
│       │           │            │              ▼            │     │
│   trigger()   入队更新    微任务执行    同步执行回调    patch    │
│                                                                  │
│   💡 watchEffect 默认在 DOM 更新前同步执行                       │
│   💡 可通过 flush: 'post' 改为 DOM 更新后执行                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

4.3 副作用对比表

特性useEffectwatchEffectwatch
依赖追踪手动声明数组自动追踪显式指定
执行时机异步(绘制后)同步(更新前)可配置
获取旧值需要 useRef✅ 回调参数
清理函数return 返回onCleanup 参数onCleanup
立即执行首次渲染后立即执行immediate 选项
闭包陷阱⚠️ 存在✅ 不存在✅ 不存在

4.4 闭包陷阱详解

// ==================== React 闭包陷阱 ====================
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count);  // 😱 永远是 0!
    }, 1000);
    return () => clearInterval(timer);
  }, []);  // 空依赖,只在 mount 时创建
  
  // 解决方案1:添加依赖
  // }, [count]);
  
  // 解决方案2:使用 ref
  // const countRef = useRef(count);
  // countRef.current = count;
}

// ==================== Vue 无闭包陷阱 ====================
const count = ref(0);

watchEffect(() => {
  setInterval(() => {
    console.log(count.value);  // ✅ 始终是最新值!
  }, 1000);
});
// Vue 的 ref 是响应式对象,读取 .value 时总是最新值

五、虚拟 DOM:Fiber 链表 vs VNode 数组

5.1 数据结构对比

// ==================== React Fiber ====================
interface FiberNode {
  tag: number;              // 组件类型
  type: any;                // 组件函数/类/标签名
  key: string | null;
  
  // 🔥 链表结构(便于中断恢复)
  return: Fiber | null;     // 父节点
  child: Fiber | null;      // 第一个子节点
  sibling: Fiber | null;    // 兄弟节点
  
  // 状态
  memoizedState: any;       // Hooks 链表
  memoizedProps: any;
  
  // 副作用
  flags: number;            // 标记(Placement/Update/Deletion)
  subtreeFlags: number;     // 🔥 子树标记(冒泡优化)
  
  // 🔥 双缓冲
  alternate: Fiber | null;  // 另一棵树的对应节点
}

// ==================== Vue VNode ====================
interface VNode {
  type: VNodeTypes;         // 节点类型
  props: VNodeProps | null;
  key: string | number | null;
  
  // 🔥 数组结构
  children: VNodeChildren;
  
  // DOM 相关
  el: Element | null;       // 真实 DOM
  
  // 🔥 编译优化标记
  shapeFlag: number;        // 节点形状
  patchFlag: number;        // patch 优化标记
  dynamicProps: string[];   // 动态属性列表
  dynamicChildren: VNode[]; // 🔥 Block Tree:只追踪动态节点
}

5.2 结构可视化

┌─────────────────────────────────────────────────────────────────┐
│                    React Fiber 树(链表)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                        App                                       │
│                         │                                        │
│                       child                                      │
│                         ▼                                        │
│                       Header ──sibling──► Main ──sibling──► Footer
│                         │                  │                     │
│                       child              child                   │
│                         ▼                  ▼                     │
│                       Logo              Content                  │
│                                                                  │
│   遍历顺序:App → Header → Logo → MainContentFooter       │
│   💡 链表结构可以随时中断,记住位置后继续                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue VNode 树(数组)                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                        App                                       │
│                         │                                        │
│                     children                                     │
│                         ▼                                        │
│              [Header, Main, Footer]                              │
│                  │       │      │                                │
│              children children children                          │
│                  ▼       ▼      ▼                                │
│               [Logo] [Content]  []                               │
│                                                                  │
│   💡 数组结构更直观,但不支持中断                                │
│   💡 通过 dynamicChildren 只追踪动态节点                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

5.3 虚拟 DOM 对比表

特性React FiberVue VNode
树结构链表(child/sibling/return)数组(children)
可中断✅ 链表便于中断恢复❌ 同步渲染
双缓冲✅ current/workInProgress❌ 直接复用
编译优化❌ 纯运行时✅ PatchFlags/Block Tree
静态提升✅ 编译时提升到函数外
内存占用较大(双树 + 链表指针)较小

六、Diff 算法:三轮遍历 vs 双端 + 最长递增子序列

6.1 算法流程对比

┌─────────────────────────────────────────────────────────────────┐
│                    React Diff(三轮遍历)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   旧: [A, B, C, D, E]                                           │
│   新: [A, C, D, B, F]                                           │
│                                                                  │
│   第一轮:从左到右,处理更新                                     │
│   ─────────────────────────                                      │
│   A === A ✓ patch                                               │
│   B !== C ✗ 跳出                                                │
│                                                                  │
│   第二轮:处理新增/删除                                          │
│   ─────────────────────────                                      │
│   旧节点没遍历完,新节点没遍历完 → 进入第三轮                    │
│                                                                  │
│   第三轮:处理移动(Map + lastPlacedIndex)                      │
│   ─────────────────────────                                      │
│   建立 Map: { B:1, C:2, D:3, E:4 }                              │
│   遍历新节点 [C, D, B, F]:                                      │
│   - C: oldIndex=2, lastPlacedIndex=0 → 不移动, lastPlacedIndex=2│
│   - D: oldIndex=3 > 2 → 不移动, lastPlacedIndex=3               │
│   - B: oldIndex=1 < 3 → 🔥 需要移动                             │
│   - F: 不在 Map → 新增                                          │
│   - E: 在 Map 但不在新列表 → 删除                               │
│                                                                  │
│   结果:移动 B,新增 F,删除 E                                   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue Diff(双端 + LIS)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   旧: [A, B, C, D, E]                                           │
│   新: [A, C, D, B, F]                                           │
│                                                                  │
│   Step 1:头部同步                                               │
│   ─────────────────────────                                      │
│   A === A ✓ patch, i++                                          │
│                                                                  │
│   Step 2:尾部同步                                               │
│   ─────────────────────────                                      │
│   E !== F ✗ 停止                                                │
│                                                                  │
│   Step 3:中间乱序部分                                           │
│   ─────────────────────────                                      │
│   旧: [B, C, D, E]  新: [C, D, B, F]                            │
│                                                                  │
│   建立 keyToNewIndexMap: { C:0, D:1, B:2, F:3 }                 │
│   newIndexToOldIndexMap: [2, 3, 1, 0]  (C在旧2, D在旧3, B在旧1) │
│                                                                  │
│   🔥 最长递增子序列: [2, 3] → 索引 [0, 1]                       │
│   - C (索引0) 在 LIS 中 → 不移动                                │
│   - D (索引1) 在 LIS 中 → 不移动                                │
│   - B (索引2) 不在 LIS → 需要移动                               │
│   - F (索引3) oldIndex=0 → 新增                                 │
│   - E 不在新列表 → 删除                                         │
│                                                                  │
│   结果:移动 B,新增 F,删除 E(与 React 相同,但移动更少)      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

6.2 移动次数对比示例

旧: [A, B, C, D]
新: [A, D, B, C]

┌─────────────────────────────────────────────────────────────────┐
│                    React(lastPlacedIndex)                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   A: oldIndex=0, lastPlacedIndex=0 → 不移动                     │
│   D: oldIndex=3, lastPlacedIndex=3 → 不移动                     │
│   B: oldIndex=1 < 3 → 🔥 移动                                   │
│   C: oldIndex=2 < 3 → 🔥 移动                                   │
│                                                                  │
│   结果:移动 2 次(B 和 C)                                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue(最长递增子序列)                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   newIndexToOldIndexMap: [1, 4, 2, 3]  (A=1, D=4, B=2, C=3)     │
│   最长递增子序列: [1, 2, 3] → 索引 [0, 2, 3]                    │
│                                                                  │
│   A (索引0) 在 LIS → 不移动                                     │
│   D (索引1) 不在 LIS → 🔥 移动                                  │
│   B (索引2) 在 LIS → 不移动                                     │
│   C (索引3) 在 LIS → 不移动                                     │
│                                                                  │
│   结果:移动 1 次(只移动 D)                                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

6.3 Diff 算法对比表

特性ReactVue
算法三轮遍历 + Map双端对比 + 最长递增子序列
移动判断lastPlacedIndex最长递增子序列
时间复杂度O(n)O(n log n)(LIS 用二分)
移动次数可能较多理论最少
实现复杂度简单较复杂

七、调度机制:时间切片 vs 微任务批量

7.1 调度策略对比

┌─────────────────────────────────────────────────────────────────┐
│                    React Scheduler(时间切片)                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   优先级(5 个级别):                                           │
│   ┌────────────────────────────────────────────────────────┐    │
│   │ ImmediatePriority   │ -1ms    │ 立即执行(同步)        │    │
│   │ UserBlockingPriority│ 250ms   │ 用户交互(点击、输入)  │    │
│   │ NormalPriority      │ 5000ms  │ 普通更新(setState)    │    │
│   │ LowPriority         │ 10000ms │ 低优先级               │    │
│   │ IdlePriority        │ ∞       │ 空闲执行               │    │
│   └────────────────────────────────────────────────────────┘    │
│                                                                  │
│   时间切片(5ms 一片):                                         │
│   ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐                              │
│   │ 5ms │→│ 5ms │→│ 5ms │→│ 5ms │→ ...                         │
│   │work │ │work │ │work │ │work │                              │
│   └─────┘ └─────┘ └─────┘ └─────┘                              │
│       ↓       ↓       ↓       ↓                                 │
│     yield   yield   yield   yield  ← 让出主线程                 │
│                                                                  │
│   💡 高优先级可以打断低优先级                                    │
│   💡 用户交互不会被长任务阻塞                                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue Scheduler(微任务批量)                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   同步代码执行:                                                 │
│   ┌────────────────────────────────────────────────────────┐    │
│   │ state.a = 1  → trigger → queueJob(update)              │    │
│   │ state.b = 2  → trigger → 已入队,跳过                   │    │
│   │ state.c = 3  → trigger → 已入队,跳过                   │    │
│   └────────────────────────────────────────────────────────┘    │
│                          │                                       │
│                          ▼                                       │
│   微任务执行:Promise.resolve().then(flushJobs)                  │
│   ┌────────────────────────────────────────────────────────┐    │
│   │ 1. 按组件 id 排序(父组件先于子组件)                   │    │
│   │ 2. 依次执行 update 任务                                 │    │
│   │ 3. 执行 postFlushCbs(mounted/updated)                 │    │
│   └────────────────────────────────────────────────────────┘    │
│                                                                  │
│   💡 多次修改只触发一次更新                                      │
│   💡 简单高效,但不支持中断                                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

7.2 核心代码对比

// ==================== React Scheduler ====================
function workLoop() {
  while (task !== null && !shouldYield()) {  // 🔥 5ms 检查一次
    task = performTask(task);
  }
  if (task !== null) {
    // 还有任务,继续调度
    scheduleCallback(task);
  }
}

function shouldYield() {
  return getCurrentTime() - startTime > 5;  // 5ms 时间片
}

// 使用 MessageChannel 避免 setTimeout 的 4ms 延迟
const channel = new MessageChannel();
channel.port1.onmessage = performWorkUntilDeadline;

// ==================== Vue Scheduler ====================
function queueJob(job) {
  if (!queue.includes(job)) {
    // 🔥 二分查找插入位置,保持按 id 排序
    queue.splice(findInsertionIndex(job.id), 0, job);
    queueFlush();
  }
}

function queueFlush() {
  if (!currentFlushPromise) {
    // 🔥 使用微任务
    currentFlushPromise = Promise.resolve().then(flushJobs);
  }
}

function flushJobs() {
  // 按 id 排序执行(父组件先于子组件)
  for (const job of queue) {
    job();
  }
  queue.length = 0;
  flushPostFlushCbs();  // 执行 mounted/updated
}

7.3 调度对比表

特性ReactVue
调度策略优先级 + 时间切片微任务批量更新
可中断✅ Concurrent Mode❌ 同步渲染
优先级5 个级别无(按组件 id 排序)
时间切片✅ 5ms 一片
实现方式MessageChannelPromise.resolve()
任务队列最小堆数组(二分插入)
复杂度

八、编译优化:运行时 vs 编译时

8.1 编译对比

// ==================== React:纯运行时 ====================
// 你写的 JSX
<div className="container">
  <span>{msg}</span>
  <p>static text</p>
</div>

// 编译后(只是语法转换)
React.createElement("div", { className: "container" },
  React.createElement("span", null, msg),
  React.createElement("p", null, "static text")
);

// 💡 每次渲染都创建新对象,需要完整 Diff
// 💡 静态内容也参与 Diff

// ==================== Vue:编译时优化 ====================
// 你写的 Template
<template>
  <div class="container">
    <span>{{ msg }}</span>
    <p>static text</p>
  </div>
</template>

// 编译后(深度优化)
// 🔥 静态提升:静态节点只创建一次
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static text", -1)

function render(_ctx) {
  return (_openBlock(), _createElementBlock("div", { class: "container" }, [
    _createElementVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
    _hoisted_1  // 🔥 复用静态节点
  ]))
}

// 💡 PatchFlag = 1 表示只有文本是动态的
// 💡 静态节点被提升,不参与 Diff

8.2 Block Tree 优化

┌─────────────────────────────────────────────────────────────────┐
│                    Vue Block Tree                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   传统 Diff:遍历整棵树                                          │
│   ┌─────────────────────────────────────────┐                   │
│   │              div                         │                   │
│   │            /  |  \                       │                   │
│   │         span  p   ul                     │  ← 需要遍历所有   │
│   │          |       / | \                   │                   │
│   │        {msg}   li li li                  │                   │
│   └─────────────────────────────────────────┘                   │
│                                                                  │
│   Block Tree:只追踪动态节点                                     │
│   ┌─────────────────────────────────────────┐                   │
│   │              div (Block)                 │                   │
│   │                 │                        │                   │
│   │         dynamicChildren: [               │  ← 只遍历动态    ││   │           span (TEXT),                   │                   ││   │         ]                                │                   │
│   └─────────────────────────────────────────┘                   │
│                                                                  │
│   💡 静态节点完全跳过,只 patch 动态节点                         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

8.3 PatchFlags 详解

// Vue 编译时标记动态内容类型
enum PatchFlags {
  TEXT = 1,           // 动态文本:{{ msg }}
  CLASS = 2,          // 动态 class::class="cls"
  STYLE = 4,          // 动态 style::style="stl"
  PROPS = 8,          // 动态 props::id="id"
  FULL_PROPS = 16,    // 有动态 key
  NEED_HYDRATION = 32,
  STABLE_FRAGMENT = 64,
  KEYED_FRAGMENT = 128,
  UNKEYED_FRAGMENT = 256,
  NEED_PATCH = 512,
  DYNAMIC_SLOTS = 1024,
  HOISTED = -1,       // 🔥 静态提升
  BAIL = -2           // 退出优化模式
}

// patch 时根据 flag 精准更新
if (patchFlag & PatchFlags.TEXT) {
  // 只更新文本
  hostSetElementText(el, newChildren);
}
if (patchFlag & PatchFlags.CLASS) {
  // 只更新 class
  hostPatchProp(el, 'class', null, newProps.class);
}

8.4 编译优化对比表

优化ReactVue
静态提升✅ 静态节点只创建一次
PatchFlags✅ 标记动态内容类型
Block Tree✅ 只追踪动态节点
事件缓存❌ 需 useCallback✅ 自动缓存
预字符串化✅ 连续静态节点合并为字符串
编译时优化显著减少运行时开销

九、组件更新流程:完整对比

9.1 React 更新流程

┌─────────────────────────────────────────────────────────────────┐
│                    React 完整更新流程                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   setState(newValue)                                            │
│        │                                                         │
│        ▼                                                         │
│   dispatchSetState()                                            │
│        │                                                         │
│        ├── 🔥 Eager State:提前计算,相同值跳过                  │
│        │                                                         │
│        ▼                                                         │
│   scheduleUpdateOnFiber() ← 标记 Fiber 需要更新                 │
│        │                                                         │
│        ▼                                                         │
│   ensureRootIsScheduled() ← 确保根节点被调度                    │
│        │                                                         │
│        ▼                                                         │
│   Scheduler.scheduleCallback(priority, callback)                │
│        │                                                         │
│        ▼                                                         │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │              Render 阶段(可中断)                        │   │
│   │                                                          │   │
│   │   workLoopConcurrent()                                   │   │
│   │        │                                                 │   │
│   │        ▼                                                 │   │
│   │   performUnitOfWork(fiber)                               │   │
│   │        │                                                 │   │
│   │        ├── beginWork() ← 递:执行组件函数,处理 Hooks    │   │
│   │        │                                                 │   │
│   │        └── completeWork() ← 归:创建 DOM,收集副作用     │   │
│   │                                                          │   │
│   │   💡 每处理一个 Fiber 检查 shouldYield()                 │   │
│   │   💡 超时则让出主线程,下次继续                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│        │                                                         │
│        ▼                                                         │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │              Commit 阶段(不可中断)                      │   │
│   │                                                          │   │
│   │   Before Mutation ← getSnapshotBeforeUpdate              │   │
│   │        │                                                 │   │
│   │        ▼                                                 │   │
│   │   Mutation ← DOM 操作(增删改)                          │   │
│   │        │                                                 │   │
│   │        ▼                                                 │   │
│   │   🔥 root.current = finishedWork ← 切换 Fiber 树        │   │
│   │        │                                                 │   │
│   │        ▼                                                 │   │
│   │   Layout ← useLayoutEffect、componentDidMount            │   │
│   └─────────────────────────────────────────────────────────┘   │
│        │                                                         │
│        ▼                                                         │
│   flushPassiveEffects() ← useEffect(异步,浏览器绘制后)       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

9.2 Vue 更新流程

┌─────────────────────────────────────────────────────────────────┐
│                    Vue 完整更新流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   state.count = newValue                                        │
│        │                                                         │
│        ▼                                                         │
│   Proxy set 拦截                                                │
│        │                                                         │
│        ▼                                                         │
│   trigger(target, key) ← 触发依赖                               │
│        │                                                         │
│        ▼                                                         │
│   遍历 Dep.subs,调用 effect.scheduler()                        │
│        │                                                         │
│        ▼                                                         │
│   queueJob(componentUpdate) ← 入队更新任务                      │
│        │                                                         │
│        ├── 🔥 去重:相同任务不重复入队                           │
│        ├── 🔥 排序:按组件 id 排序(父先于子)                   │
│        │                                                         │
│        ▼                                                         │
│   queueFlush() ← Promise.resolve().then(flushJobs)              │
│        │                                                         │
│        ▼                                                         │
│   ─────────── 同步代码结束,微任务开始 ───────────               │
│        │                                                         │
│        ▼                                                         │
│   flushJobs()                                                   │
│        │                                                         │
│        ▼                                                         │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │              组件更新                                     │   │
│   │                                                          │   │
│   │   componentUpdateFn()                                    │   │
│   │        │                                                 │   │
│   │        ├── onBeforeUpdate hooks                          │   │
│   │        │                                                 │   │
│   │        ├── render() ← 生成新 VNode                       │   │
│   │        │                                                 │   │
│   │        ├── patch(prevTree, nextTree) ← Diff + DOM 更新   │   │
│   │        │        │                                        │   │
│   │        │        ├── 🔥 Block Tree:只遍历动态节点        │   │
│   │        │        └── 🔥 PatchFlags:精准更新              │   │
│   │        │                                                 │   │
│   │        └── queuePostFlushCb(onUpdated)                   │   │
│   └─────────────────────────────────────────────────────────┘   │
│        │                                                         │
│        ▼                                                         │
│   flushPostFlushCbs() ← onMounted/onUpdated hooks               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

9.3 更新流程对比表

阶段ReactVue
触发更新setState → dispatchSetStateProxy set → trigger
调度方式Scheduler + 优先级微任务 + 去重排序
渲染阶段Render(可中断)同步执行
DOM 操作Commit(不可中断)patch(同步)
副作用执行useEffect(异步)watchEffect(可配置)
生命周期Layout 阶段执行postFlushCbs 执行

十、生命周期对比

┌─────────────────────────────────────────────────────────────────┐
│                    生命周期映射                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   React Class          React Hooks           Vue 3               │
│   ───────────          ───────────           ─────               │
│   constructor          -                     setup()             │
│   render               return JSX            render()/template   │
│   componentDidMount    useEffect(,[])        onMounted           │
│   componentDidUpdate   useEffect             onUpdated           │
│   componentWillUnmount useEffect return      onBeforeUnmount     │
│   -                    -                     onUnmounted         │
│   -                    useLayoutEffect       onBeforeMount+Mounted│
│   getDerivedState      -                     watch + computed    │
│   shouldComponentUpdate React.memo           -(自动优化)        │
│   getSnapshotBefore    -                     onBeforeUpdate      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    执行时机对比                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   React:                                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ Render → Commit(Layout) → 浏览器绘制 → useEffect        │   │
│   │                  ↑                           ↑          │   │
│   │          useLayoutEffect              异步执行          │   │
│   │          componentDidMount                              │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│   Vue:                                                          │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │ setup → beforeMount → patch → mounted → 浏览器绘制      │   │
│   │                         ↑        ↑                      │   │
│   │                    DOM更新   postFlushCbs               │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

十一、性能对比

11.1 Benchmark 实测数据

┌─────────────────────────────────────────────────────────────────┐
                    js-framework-benchmark 实测                   
                    (Chrome 120, 越低越好,单位 ms)               
├─────────────────────────────────────────────────────────────────┤
                                                                  
   创建 1000                                                    
   React 19:    ████████████████████  45.2ms                     
   Vue 3.5:     ████████████████      38.6ms                     
                                                                  
   更新 1000 行(每行修改)                                       
   React 19:    ████████████████████████  52.8ms                 
   Vue 3.5:     ██████████████            31.4ms   🔥  40%    
                                                                  
   部分更新(每 10 行修改 1 行)                                  
   React 19:    ████████████████████  44.1ms                     
   Vue 3.5:     ████████              18.2ms   🔥  59%        
                                                                  
   交换两行                                                       
   React 19:    ████████████████  35.6ms                         
   Vue 3.5:     ████████████      28.3ms                         
                                                                  
   删除一行                                                       
   React 19:    ████████████  26.4ms                             
   Vue 3.5:     ██████████    22.1ms                             
                                                                  
   内存占用(创建 1000 行后)                                     
   React 19:    ████████████████████  8.2MB                      
   Vue 3.5:     ████████████          5.1MB   🔥  38%         
                                                                  
└─────────────────────────────────────────────────────────────────┘

💡 Vue 在部分更新场景优势明显(编译优化 + 精准更新)
💡 React 在复杂交互场景更稳定(时间切片避免卡顿)

11.2 性能差异根因分析

┌─────────────────────────────────────────────────────────────────┐
                    为什么 Vue 部分更新更快?                     
├─────────────────────────────────────────────────────────────────┤
                                                                  
   React 更新 1  item:                                        
   ┌─────────────────────────────────────────────────────────┐   
    1. setState 触发                                            
    2. 整个 List 组件重新执行                                   
    3. 创建 1000 个新的 JSX 对象                                
    4. Diff 1000 个节点,找出 1 个变化                          
    5. 更新 1  DOM                                            
   └─────────────────────────────────────────────────────────┘   
                                                                  
   Vue 更新 1  item:                                          
   ┌─────────────────────────────────────────────────────────┐   
    1. Proxy set 触发                                           
    2. 找到依赖这个数据的 1 个组件                              
    3. 只重新渲染这 1  Item 组件                              
    4. Block Tree:只 Diff 动态节点                             
    5. 更新 1  DOM                                            
   └─────────────────────────────────────────────────────────┘   
                                                                  
   💡 Vue 的优势:精准定位 + 最小化渲染范围                      
                                                                  
└─────────────────────────────────────────────────────────────────┘

11.3 性能对比表

维度ReactVue说明
首次渲染较快较快差异不大
更新性能组件级 Diff精准更新Vue 更新粒度更细
大列表需要虚拟滚动需要虚拟滚动都需要优化
内存占用Fiber 双树开销VNode 开销较小Vue 3.5 优化后更明显
包体积~42KB~33KBVue 更小
编译优化显著Vue 编译时优化明显
手动优化useMemo/useCallback基本不需要React 需要更多手动优化

十二、SSR/SSG 对比:服务端渲染的差异

12.1 架构对比

┌─────────────────────────────────────────────────────────────────┐
│                    React SSR 架构(Next.js)                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    Server Components                     │   │
│   │                                                          │   │
│   │   • 默认在服务端执行,不发送 JS 到客户端                 │   │
│   │   • 可以直接访问数据库、文件系统                         │   │
│   │   • 不能使用 useState、useEffect                         │   │
│   │                                                          │   │
│   │   async function Page() {                                │   │
│   │     const data = await db.query('SELECT * FROM posts');  │   │
│   │     return <PostList posts={data} />;                    │   │
│   │   }                                                      │   │
│   └─────────────────────────────────────────────────────────┘   │
│                          │                                       │
│                          ▼                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    Client Components                     │   │
│   │                                                          │   │
│   │   'use client';  // 🔥 显式标记                          │   │
│   │                                                          │   │
│   │   function LikeButton() {                                │   │
│   │     const [liked, setLiked] = useState(false);           │   │
│   │     return <button onClick={() => setLiked(true)}>      │   │
│   │   }                                                      │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│   💡 React 的 RSC 是革命性的:组件级别的服务端/客户端分离        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Vue SSR 架构(Nuxt 3)                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    Universal Components                  │   │
│   │                                                          │   │
│   │   • 同一份代码,服务端和客户端都执行                     │   │
│   │   • 通过 useAsyncData/useFetch 获取数据                  │   │
│   │   • 自动处理 hydration                                   │   │
│   │                                                          │   │
│   │   <script setup>                                         │   │
│   │   const { data } = await useFetch('/api/posts');         │   │
│   │   </script>                                              │   │
│   └─────────────────────────────────────────────────────────┘   │
│                          │                                       │
│                          ▼                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                    Client Only                           │   │
│   │                                                          │   │
│   │   <ClientOnly>                                           │   │
│   │     <BrowserOnlyComponent />                             │   │
│   │   </ClientOnly>                                          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│   💡 Vue 的 SSR 更传统:同构渲染,代码复用率高                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

12.2 数据获取对比

// ==================== Next.js (React) ====================
// Server Component:直接 async/await
async function PostPage({ params }) {
  const post = await db.post.findUnique({ where: { id: params.id } });
  return <Post data={post} />;
}

// Client Component:需要 use() 或 useEffect
'use client';
function Comments({ postId }) {
  const [comments, setComments] = useState([]);
  useEffect(() => {
    fetch(`/api/comments/${postId}`).then(r => r.json()).then(setComments);
  }, [postId]);
  return <CommentList comments={comments} />;
}

// ==================== Nuxt 3 (Vue) ====================
// 统一的数据获取 API
<script setup>
// 服务端获取,自动序列化到客户端
const { data: post } = await useFetch(`/api/posts/${route.params.id}`);

// 仅客户端获取
const { data: comments } = await useFetch(`/api/comments`, {
  server: false  // 🔥 只在客户端执行
});
</script>

12.3 SSR 对比表

特性React (Next.js)Vue (Nuxt 3)
渲染模式RSC + Client ComponentsUniversal Components
数据获取async 组件 / use()useFetch / useAsyncData
代码分割自动(Server/Client)手动(ClientOnly)
Streaming✅ 原生支持✅ 支持
部分 Hydration✅ RSC 天然支持✅ Lazy Hydration
学习曲线较陡(RSC 概念新)较平缓
心智模型需要区分 Server/Client同构思维

十三、状态管理对比:Redux vs Pinia

13.1 设计哲学

┌─────────────────────────────────────────────────────────────────┐
│                    Redux:单向数据流 + 不可变                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────┐    dispatch    ┌─────────┐    new state           │
│   │  View   │ ─────────────► │ Reducer │ ─────────────►         │
│   └─────────┘                └─────────┘                        │
│        ▲                          │                              │
│        │                          │                              │
│        └──────────────────────────┘                              │
│                   subscribe                                      │
│                                                                  │
│   // 必须返回新对象                                              │function reducer(state, action) {                             │
│     switch (action.type) {                                      │
│       case 'increment':                                         │
│         return { ...state, count: state.count + 1 };            │
│     }                                                            │
│   }                                                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Pinia:响应式 + 直接修改                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   ┌─────────┐    直接修改     ┌─────────┐    自动更新           │
│   │  View   │ ─────────────► │  Store  │ ─────────────►         │
│   └─────────┘                └─────────┘                        │
│        ▲                          │                              │
│        │                          │                              │
│        └──────────────────────────┘                              │
│                   响应式追踪                                     │
│                                                                  │
│   // 直接修改,自动响应                                          │const store = useCounterStore();                              │
│   store.count++;  // 🔥 就这么简单                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

13.2 代码对比

// ==================== Redux Toolkit ====================
// store.js
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0, loading: false },
  reducers: {
    increment: (state) => { state.count += 1 },  // Immer 允许"可变"写法
    decrement: (state) => { state.count -= 1 },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchCount.pending, (state) => { state.loading = true })
      .addCase(fetchCount.fulfilled, (state, action) => {
        state.count = action.payload;
        state.loading = false;
      });
  },
});

// 异步 action
const fetchCount = createAsyncThunk('counter/fetch', async () => {
  const response = await api.getCount();
  return response.data;
});

// 组件中使用
function Counter() {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();
  
  return (
    <button onClick={() => dispatch(increment())}>
      {count}
    </button>
  );
}

// ==================== Pinia ====================
// stores/counter.js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);
  const loading = ref(false);
  
  const doubleCount = computed(() => count.value * 2);
  
  function increment() {
    count.value++;
  }
  
  async function fetchCount() {
    loading.value = true;
    count.value = await api.getCount();
    loading.value = false;
  }
  
  return { count, loading, doubleCount, increment, fetchCount };
});

// 组件中使用
<script setup>
const store = useCounterStore();
</script>

<template>
  <button @click="store.increment()">{{ store.count }}</button>
</template>

13.3 状态管理对比表

特性Redux (Toolkit)Pinia
设计理念单向数据流、不可变响应式、可变
样板代码较多(slice/reducer)很少
TypeScript需要额外配置原生支持,自动推断
异步处理createAsyncThunk直接 async/await
DevTools✅ 时间旅行✅ 时间旅行
模块化需要 combineReducers天然模块化
学习曲线较陡平缓
包体积~11KB~1.5KB

十四、适用场景

选择 React

  • ✅ 大型复杂应用,需要精细控制
  • ✅ 团队熟悉函数式编程
  • ✅ 需要 React Native 跨端
  • ✅ 需要丰富的生态(Next.js、Remix)
  • ✅ 需要 Server Components(RSC)
  • ✅ 需要 Concurrent Mode 特性(时间切片、Suspense)
  • ✅ 需要更灵活的架构设计

选择 Vue

  • ✅ 中小型应用,快速开发
  • ✅ 团队偏好模板语法
  • ✅ 需要更低的学习曲线
  • ✅ 需要更好的开箱即用体验
  • ✅ 需要更小的包体积和内存占用
  • ✅ 需要更少的手动优化
  • ✅ 国内团队(中文文档完善)

十五、总结

维度ReactVue胜出
响应式不可变 + setStateProxy + 依赖追踪Vue 更自动
副作用useEffect 手动依赖watch 自动追踪Vue 更智能
虚拟 DOMFiber 链表VNode 数组React 可中断
Diff 算法三轮遍历双端 + LISVue 移动更少
调度优先级 + 时间切片微任务批量React 更强大
编译优化PatchFlags/BlockVue 更高效
SSRRSC 革命性同构成熟各有优势
状态管理Redux 严谨Pinia 简洁Pinia 更易用
性能稳定部分更新更快Vue 3.5 优势明显
心智模型函数式响应式各有所好
生态更丰富更统一各有优势
学习曲线较陡较平缓Vue 更友好

核心差异一句话总结

  • React 19:拥抱不可变性,Server Components 革命,通过 Fiber 实现可中断渲染,运行时 Diff,需要手动优化
  • Vue 3.5:拥抱响应式,编译时优化减少 56% 内存,精准更新,自动优化

我的建议

┌─────────────────────────────────────────────────────────────────┐
│                    2025 年选型建议                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   选 React 如果:                                                │
│   • 需要 React Native 做移动端                                  │
│   • 团队有 React 经验,项目已用 React                           │
│   • 需要 Server Components 的革命性架构                         │
│   • 大型应用,需要精细的性能调优                                │
│                                                                  │
│   选 Vue 如果:                                                  │
│   • 新项目,追求开发效率                                        │
│   • 团队规模不大,需要快速上手                                  │
│   • 对包体积和内存敏感                                          │
│   • 不想花太多精力在性能优化上                                  │
│                                                                  │
│   💡 两个框架都是顶级选择,关键是匹配团队和项目                 │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

📦 源码参考:

📚 相关专栏:

如果这篇文章对你有帮助,欢迎点赞收藏!有问题评论区见 🎉