返回首页

Vue3.0 的设计目标是什么?做了哪些优化?

问题解析

Vue3 经过长达两三年的筹备,带来了全方位的改进。面试考察这个问题,是想要了解候选人对 Vue3 整体架构升级的理解,以及能否从源码、性能、语法等多个维度分析优化点。

核心概念

Vue3 的设计目标

┌─────────────────────────────────────────┐
│           Vue3 设计目标                  │
├─────────────┬─────────────┬─────────────┤
│    更小     │    更快     │   更友好    │
├─────────────┼─────────────┼─────────────┤
│ • Tree      │ • 编译优化   │ • TS支持    │
│   Shaking   │ • Proxy响应式│ • Composition│
│ • 移除不常用 │ • Diff优化  │   API       │
│   API       │ • SSR优化   │ • 更好的    │
│             │             │   错误提示  │
└─────────────┴─────────────┴─────────────┘

解决的问题

  • 复杂组件代码难以维护
  • 缺少干净的逻辑复用机制
  • TypeScript 支持不够友好
  • 打包体积过大

详细解答

一、更小(Smaller)

1. Tree Shaking 支持

// Vue2: 无论使用什么功能,全部打包
import Vue from 'vue';
Vue.nextTick(() => {});
// 打包结果包含 Vue 所有功能

// Vue3: 按需引入,未使用功能不打包
import { nextTick, ref, computed } from 'vue';
nextTick(() => {});
// 只有使用到的功能才会被打包

效果对比:

项目 Vue2 Vue3(按需)
基础运行时 ~23KB ~10KB(最小)
使用 computed 已包含 + ~0.5KB
使用 watch 已包含 + ~0.5KB

2. 移除不常用 API

// Vue3 移除的 API
Vue.set           // 不再需要,Proxy 自动监听
Vue.delete        // 不再需要
Vue.prototype.$on // 事件总线模式移除
Vue.filter        // 过滤器移除
Vue.component     // 全局组件注册方式改变

二、更快(Faster)

1. 编译阶段优化

Diff 算法优化 - 静态标记(PatchFlag):

<template>
  <div>
    <p>静态文本1</p>      <!-- 静态节点,标记 HOISTED (-1) -->
    <p>静态文本2</p>      <!-- 静态节点,跳过 Diff -->
    <p>{{ message }}</p>  <!-- 动态节点,标记 TEXT (1) -->
    <p :class="cls">静态</p> <!-- 动态节点,标记 CLASS (2) -->
  </div>
</template>
// 编译后的代码(简化)
const _hoisted_1 = /*#__PURE__*/createVNode("p", null, "静态文本1", -1 /* HOISTED */);
const _hoisted_2 = /*#__PURE__*/createVNode("p", null, "静态文本2", -1 /* HOISTED */);

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,  // 静态节点直接复用
    _hoisted_2,  // 不需要 Diff
    _createVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */),
    _createVNode("p", { class: _ctx.cls }, null, 2 /* CLASS */)
  ]));
}

静态提升(Hoist Static):

<template>
  <div>
    <span>你好</span>
    <div>{{ message }}</div>
  </div>
</template>
// 优化前:每次渲染都创建静态节点
function render() {
  return createVNode('div', null, [
    createVNode('span', null, '你好'),  // 每次重新创建
    createVNode('div', null, message)
  ]);
}

// 优化后:静态节点提升到 render 函数外
const _hoisted_1 = createVNode('span', null, '你好', -1 /* HOISTED */);

function render() {
  return createVNode('div', null, [
    _hoisted_1,  // 直接复用,不重新创建
    createVNode('div', null, message, 1 /* TEXT */)
  ]);
}

事件监听缓存:

<template>
  <button @click="onClick">点击</button>
</template>
// 未开启缓存 - 每次都被视为动态绑定
_createVNode("button", {
  onClick: _ctx.onClick  // 每次都会追踪变化
}, "点击", 8 /* PROPS */, ["onClick"]);

// 开启缓存后 - 只在首次创建
_createVNode("button", {
  onClick: _cache[1] || (_cache[1] = (...args) => _ctx.onClick(...args))
  // 使用缓存,无 PatchFlag
}, "点击");

2. 响应式系统优化

Vue2 - Object.defineProperty:

// 需要递归遍历所有属性
defineReactive(data) {
  Object.keys(data).forEach(key => {
    const val = data[key];
    observe(val); // 递归监听嵌套对象
    Object.defineProperty(data, key, {
      get() { /* ... */ },
      set() { /* ... */ }
    });
  });
}

// 问题:
// 1. 初始化时深度遍历,性能开销大
// 2. 无法监听新增/删除属性
// 3. 数组需要重写方法

Vue3 - Proxy:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      // 懒递归:访问时才建立响应式
      return isObject(res) ? reactive(res) : res;
    },
    set(target, key, value, receiver) {
      // 自动监听新增属性
      return Reflect.set(target, key, value, receiver);
    },
    deleteProperty(target, key) {
      // 自动监听删除属性
      return Reflect.deleteProperty(target, key);
    }
  });
}

对比:

特性 Vue2 defineProperty Vue3 Proxy
初始化性能 深度递归,初始化慢 懒加载,访问时才代理
新增属性 无法监听(需 Vue.set) 自动监听
删除属性 无法监听(需 Vue.delete) 自动监听
数组索引 无法监听 自动监听
数组长度 无法监听 自动监听

3. SSR 优化

<template>
  <div>
    <span>你好</span>
    <span>{{ message }}</span>
    <!-- 大量静态内容... -->
  </div>
</template>
// 编译为字符串拼接,减少创建虚拟 DOM 的开销
export function ssrRender(_ctx, _push) {
  _push(`<div><span>你好</span><span>${_ssrInterpolate(_ctx.message)}</span>...</div>`);
}

三、更友好(More Friendly)

1. Composition API

// 更灵活的逻辑复用
import { useMouse } from './useMouse';
import { useFetch } from './useFetch';

export default {
  setup() {
    const { x, y } = useMouse();
    const { data, loading } = useFetch('/api/data');

    return { x, y, data, loading };
  }
}

2. TypeScript 支持

// Vue3 完全用 TypeScript 重写
import { ref, computed, Ref } from 'vue';

interface User {
  name: string;
  age: number;
}

export default {
  setup() {
    const user = ref<User>({ name: 'Tom', age: 20 });
    const doubleAge = computed<number>(() => user.value.age * 2);

    // 完整的类型推断和提示
    const updateName = (name: string): void => {
      user.value.name = name;
    };

    return { user, doubleAge, updateName };
  }
}

深入理解

1. 源码架构优化 - Monorepo

Vue3 源码结构(monorepo)
├── packages/
│   ├── reactivity/      # 响应式系统(可独立使用)
│   ├── runtime-core/    # 运行时核心
│   ├── runtime-dom/     # DOM 运行时
│   ├── compiler-core/   # 编译器核心
│   ├── compiler-dom/    # DOM 编译器
│   ├── vue/             # 完整版入口
│   └── ...

优势:

  • 模块职责清晰
  • 可以独立使用某些包(如 @vue/reactivity
  • 便于维护和测试

2. 编译时优化 vs 运行时优化

┌─────────────────────────────────────────┐
│           Vue3 优化策略                  │
├─────────────────────────────────────────┤
│  编译时优化(Compiler)                   │
│  • 静态提升                              │
│  • 静态标记(PatchFlag)                 │
│  • 事件缓存                              │
│  • 树结构打平(Block Tree)              │
├─────────────────────────────────────────┤
│  运行时优化(Runtime)                    │
│  • Proxy 响应式                          │
│  • 更快的 Diff 算法                       │
│  • 优化过的组件实例创建                   │
└─────────────────────────────────────────┘

3. Block Tree - 树结构打平

<template>
  <div>  <!-- Block -->
    <p>静态1</p>
    <p>{{ dynamic }}</p>
    <div>   <!-- Block -->
      <span>静态2</span>
      <span>{{ another }}</span>
    </div>
  </div>
</template>
// 传统 Diff:需要遍历整棵树
// Vue3 Block Tree:只收集动态节点,打平遍历

function render() {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "静态1"),
    _createVNode("p", null, _ctx.dynamic, 1 /* TEXT */),
    (_openBlock(), _createBlock("div", null, [
      _createVNode("span", null, "静态2"),
      _createVNode("span", null, _ctx.another, 1 /* TEXT */)
    ]))
  ], 64 /* STABLE_FRAGMENT */));
}

最佳实践

1. 利用 Tree Shaking 减少体积

// ✅ 按需导入
import { ref, computed, watch } from 'vue';

// ❌ 不要全量导入(如果打包工具不支持 tree shaking)
import Vue from 'vue';

2. 合理使用编译优化

<!-- ✅ 模板中大量使用静态内容 -->
<template>
  <div class="article">
    <h1>{{ title }}</h1>  <!-- 只有这里是动态的 -->
    <div class="content">
      <p>段落1</p>  <!-- 静态,会被提升 -->
      <p>段落2</p>  <!-- 静态,会被提升 -->
      <p>段落3</p>  <!-- 静态,会被提升 -->
    </div>
  </div>
</template>

3. 选择正确的响应式 API

import { ref, reactive, shallowRef, shallowReactive } from 'vue';

// 深度响应式(默认)
const state = reactive({ nested: { count: 0 } });
// state.nested.count++ 会触发更新

// 浅层响应式(性能优化)
const shallow = shallowReactive({ nested: { count: 0 } });
// shallow.nested.count++ 不会触发更新
// 只有 shallow.nested = newValue 会触发

// 大型数据优化
const bigList = shallowRef([/* 大量数据 */]);
// 直接替换整个数组时才触发更新,内部元素变化不追踪

面试要点

  1. 能够说出 Vue3 的三大设计目标

    • 更小(Tree Shaking、移除不常用 API)
    • 更快(编译优化、Proxy 响应式)
    • 更友好(TS 支持、Composition API)
  2. 理解编译阶段的优化手段

    • 静态标记(PatchFlag)
    • 静态提升(Hoist Static)
    • 事件缓存
  3. 理解 Proxy 相比 defineProperty 的优势

    • 懒加载、自动监听新增/删除属性、数组索引和长度变化
  4. 了解 Monorepo 架构的好处

    • 模块拆分更细、职责清晰、可独立使用
  5. 能够对比 Vue2 和 Vue3 的打包体积差异

    • Vue3 按需加载,最小可至 10KB

核心结论

  • Vue3 从源码架构、编译阶段、运行时三个层面进行了全面优化
  • Tree Shaking 让打包体积显著减小
  • 编译时优化大幅提升了运行时性能
  • Proxy 让响应式系统更加完善和高效