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([/* 大量数据 */]);
// 直接替换整个数组时才触发更新,内部元素变化不追踪
面试要点
-
能够说出 Vue3 的三大设计目标
- 更小(Tree Shaking、移除不常用 API)
- 更快(编译优化、Proxy 响应式)
- 更友好(TS 支持、Composition API)
-
理解编译阶段的优化手段
- 静态标记(PatchFlag)
- 静态提升(Hoist Static)
- 事件缓存
-
理解 Proxy 相比 defineProperty 的优势
- 懒加载、自动监听新增/删除属性、数组索引和长度变化
-
了解 Monorepo 架构的好处
- 模块拆分更细、职责清晰、可独立使用
-
能够对比 Vue2 和 Vue3 的打包体积差异
- Vue3 按需加载,最小可至 10KB
核心结论:
- Vue3 从源码架构、编译阶段、运行时三个层面进行了全面优化
- Tree Shaking 让打包体积显著减小
- 编译时优化大幅提升了运行时性能
- Proxy 让响应式系统更加完善和高效