| 模块 | 职责 | 可独立使用 |
|---|---|---|
| @vue/reactivity | 响应式系统(ref/reactive/effect) | ✅ 是 |
| @vue/runtime-core | 平台无关运行时(渲染器、组件) | ✅ 是 |
| @vue/runtime-dom | DOM 渲染实现 | ❌ 否 |
| @vue/compiler-core | 模板编译器核心 | ✅ 是 |
| @vue/compiler-dom | DOM 平台编译优化 | ❌ 否 |
| @vue/compiler-sfc | 单文件组件编译 | ❌ 否 |
@vue/reactivity 可以直接在 Node.js 或其他框架中使用。
Vue3 使用 Proxy 替代 Vue2 的 Object.defineProperty:
// reactive 的实现原理 function reactive(target) { if (typeof target !== 'object' || target === null) { return target; } // 已经是 Proxy 则直接返回 if (target[ReactiveFlags.IS_REACTIVE]) { return target; } return new Proxy(target, mutableHandlers); } // 基础处理器 const mutableHandlers = { get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) return true; track(target, key); // 依赖收集 return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (hasChanged(value, oldValue)) { trigger(target, key); // 触发更新 } return result; } };
// 嵌套的 WeakMap 结构 // targetMap: { target -> { key -> Set} } const targetMap = new WeakMap(); let activeEffect = null; let shouldTrack = false; function isTracking() { return shouldTrack && activeEffect !== null; } function track(target, key) { if (!isTracking()) return; // 获取 target 对应的 depsMap let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } // 获取 key 对应的 dep(Set) let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } // 添加当前 activeEffect trackEffects(dep); } function trackEffects(dep) { if (activeEffect && !dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps.push(dep); } }
function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; // 没有被依赖的属性 const dep = depsMap.get(key); if (!dep) return; triggerEffects(dep); } function triggerEffects(dep) { for (const effect of dep) { if (effect.scheduler) { effect.scheduler(); // 调度执行(组件更新走 scheduler) } else { effect.run(); // 直接执行 } } }
// ReactiveEffect 类(核心!) class ReactiveEffect { active = true; deps = []; parent = undefined; constructor(fn, scheduler) { this.fn = fn; this.scheduler = scheduler; } run() { if (!this.active) return this.fn(); this.parent = activeEffect; activeEffect = this; shouldTrack = true; try { return this.fn(); // 执行时会触发 get,进行依赖收集 } finally { activeEffect = this.parent; this.parent = undefined; shouldTrack = false; } } stop() { if (this.active) { cleanupEffect(this); this.active = false; } } } // effect 函数 function effect(fn, options = {}) { const _effect = new ReactiveEffect(fn); if (!options.lazy) { _effect.run(); } return _effect.run.bind(_effect); }
// ref 本质上是一个带有 value 访问器的对象 class RefImpl { _value; _rawValue; dep = new Set(); constructor(value) { this._rawValue = value; this._value = toReactive(value); } get value() { trackRefValue(this); // 依赖收集 return this._value; } set value(newVal) { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = toReactive(newVal); triggerEffects(this.dep); // 触发更新 } } } function ref(value) { return new RefImpl(value); } // ref vs reactive 对比 // ref: 包装原始值,通过 .value 访问,适合基本类型 // reactive: 代理对象,直接访问属性,适合对象类型
toRefs。
Vue3 编译器会在编译阶段分析模板,标记动态节点:
// 编译前的模板 <template> <div class="static">Hello</div> <div :class="dynamicClass">World</div> <span>{{ message }}</span> </template> /// 编译后的渲染函数(简化) function render() { return (openBlock(), createElementBlock("div", null, [ createElementVNode("div", { class: "static" }, "Hello"), createElementVNode("div", { class: _ctx.dynamicClass }, "World", 2 /* CLASS */), createElementVNode("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */), ])); }
| Flag | 值 | 含义 |
|---|---|---|
| TEXT | 1 | 动态文本 |
| CLASS | 2 | 动态类名 |
| STYLE | 4 | 动态样式 |
| PROPS | 8 | 动态属性 |
| FULL_PROPS | 16 | 动态 key |
| HYDRATE_EVENTS | 32 | 事件监听 |
| STABLE_FRAGMENT | 64 | 稳定 Fragment |
| KEYED_FRAGMENT | 128 | 带 key 的 Fragment |
| UNKEYED_FRAGMENT | 256 | 无 key 的 Fragment |
| NEED_PATCH | 512 | 需要 patch |
| DYNAMIC_SLOTS | 1024 | 动态插槽 |
将静态节点提升到渲染函数外部,避免重复创建:
// 编译前 <template> <div> <div>Static 1</div> <div>Static 2</div> <div>{{ dynamic }}</div> </div> </template> // 编译后:静态节点被提升为常量 const _hoisted_1 = /*#__PURE__*/ createElementVNode("div", null, "Static 1"); const _hoisted_2 = /*#__PURE__*/ createElementVNode("div", null, "Static 2"); function render() { return (openBlock(), createElementBlock("div", null, [ _hoisted_1, // 复用 _hoisted_2, // 复用 createElementVNode("div", null, _toDisplayString(_ctx.dynamic), 1), ])); }
// 当静态节点数量很大时(默认 50 个),直接生成 innerHTML function render() { return (openBlock(), createElementBlock("div", { innerHTML: _hoisted_1 // 预字符串化的静态内容 }, null, 8 /* PROPS */, ["innerHTML"])); }
// 编译前 <template> <button @click="() => count++">Click</button> </template> // 编译后:内联箭头函数会被缓存 function render() { return (openBlock(), createElementBlock("button", { onClick: _cache[0] || (_cache[0] = ($event) => (_ctx.count++)) }, "Click")); }
// 虚拟 DOM 节点结构 const vnode = { __v_isVNode: true, type: 'div', // 标签类型 props: { id: 'app' }, // 属性 key: null, // diff 标识 children: [ // 子节点 { type: 'span', children: 'Hello' } ], shapeFlag: 11, // 形状标记(位运算) el: null, // 真实 DOM 引用 component: null, // 组件实例引用 }; // ShapeFlags(位运算标记) export const enum ShapeFlags { ELEMENT = 1, // 0001 FUNCTIONAL_COMPONENT = 1 << 1, // 0010 STATEFUL_COMPONENT = 1 << 2, // 0100 TEXT_CHILDREN = 1 << 3, // 1000 ARRAY_CHILDREN = 1 << 4, SLOTS_CHILDREN = 1 << 5, TELEPORT = 1 << 6, SUSPENSE = 1 << 7, COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, COMPONENT_KEPT_ALIVE = 1 << 9, COMPONENT = STATEFUL_COMPONENT | FUNCTIONAL_COMPONENT }
Vue3 的 Diff 算法采用双端对比策略:
// 简化的 Diff 算法 function patchKeyedChildren(c1, c2, container) { let i = 0; // 新旧的起始索引 let e1 = c1.length - 1; // 旧的结束索引 let e2 = c2.length - 1; // 新的结束索引 // 1. 从左到右扫描 while (i <= e1 && i <= e2) { const n1 = c1[i]; const n2 = c2[i]; if (isSameVNodeType(n1, n2)) { patch(n1, n2, container); // 递归 patch } else { break; } i++; } // 2. 从右到左扫描 while (i <= e1 && i <= e2) { const n1 = c1[e1]; const n2 = c2[e2]; if (isSameVNodeType(n1, n2)) { patch(n1, n2, container); } else { break; } e1--; e2--; } // 3. 处理剩余节点(新增、删除、移动) if (i > e1) { // 新节点更多 → 新增 const nextPos = e2 + 1; const anchor = nextPos < c2.length ? c2[nextPos].el : null; while (i <= e2) { patch(null, c2[i++], container, anchor); } } else if (i > e2) { // 旧节点更多 → 删除 while (i <= e1) { unmount(c1[i++], parentComponent, parentSuspense, true); } } else { // 4. 复杂情况:移动和复用 // 使用 key 的 Map 进行最长递增子序列优化 ... 后续代码省略 } }
// setup 在组件创建前执行,返回渲染函数或数据对象 function setup(props, { emit, slots, attrs, expose }) { // 响应式数据 const count = ref(0); const state = reactive({ name: 'Vue' }); // 计算属性 const double = computed(() => count.value * 2); // 监听器 watch(count, (newVal) => { console.log('count changed:', newVal); }); // 生命周期钩子 onMounted(() => { console.log('mounted'); }); // 返回的数据可以在模板中使用 return { count, state, double }; }
// computed 本质上是一个带缓存的 effect class ComputedRefImpl { _getter; _dirty = true; // 脏检查标记 _value; effect; constructor(getter) { // 创建一个 lazy effect this.effect = new ReactiveEffect(getter, () => { // 当依赖变化时,标记为 dirty if (!this._dirty) { this._dirty = true; } }); } get value() { if (this._dirty) { this._dirty = false; this._value = this.effect.run(); // 重新计算 } return this._value; } }
// watch 的核心是创建一个 effect 并监听变化 function watch(source, cb, options = {}) { let getter; // 处理不同类型的数据源 if (isRef(source)) { getter = () => source.value; } else if (isReactive(source)) { getter = () => traverse(source); // 递归访问所有属性 } else if (isFunction(source)) { getter = source; } let oldValue; // 调度函数:变化时执行 const job = () => { const newValue = effect.run(); cb(newValue, oldValue); // 调用回调 oldValue = newValue; }; const effect = new ReactiveEffect(getter, job); if (options.immediate) { job(); // 立即执行 } else { oldValue = effect.run(); // 初始化 oldValue } }
// 组件渲染的核心 effect function setupRenderEffect(instance, container, anchor) { const componentUpdateFn = () => { if (!instance.isMounted) { // 首次挂载 const subTree = (instance.subTree = renderComponentRoot(instance)); patch(null, subTree, container, anchor); instance.isMounted = true; } else { // 更新 const nextTree = renderComponentRoot(instance); const prevTree = instance.subTree; instance.subTree = nextTree; patch(prevTree, nextTree, container, anchor); } }; // 创建 ReactiveEffect,scheduler 加入更新队列 const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => queueJob(update) // 异步批量更新 )); const update = (instance.update = () => effect.run()); update(); }
function h(type, props, children) { return { type, props: props || {}, children: typeof children === 'string' ? children : children?.map(child => typeof child === 'string' ? { type: 'text', children: child } : child ), }; } // 使用示例 h('div', { id: 'app' }, [ h('span', null, 'Hello'), 'World' ]);
function render(vnode, container) { if (typeof vnode.type === 'string') { mountElement(vnode, container); } else { mountComponent(vnode, container); } } function mountElement(vnode, container) { const el = document.createElement(vnode.type); // 处理 props for (const [key, val] of Object.entries(vnode.props)) { el.setAttribute(key, val); } // 处理 children if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => render(child, el)); } container.appendChild(el); vnode.el = el; }
let activeEffect = null; class ReactiveEffect { constructor(fn) { this.fn = fn; } run() { activeEffect = this; return this.fn(); } } const targetMap = new WeakMap(); function reactive(target) { return new Proxy(target, { get(target, key) { track(target, key); return target[key]; }, set(target, key, value) { target[key] = value; trigger(target, key); return true; } }); } function track(target, key) { if (!activeEffect) return; let depsMap = targetMap.get(target); if (!depsMap) targetMap.set(target, (depsMap = new Map())); let dep = depsMap.get(key); if (!dep) depsMap.set(key, (dep = new Set())); dep.add(activeEffect); } function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); dep?.forEach(effect => effect.run()); } function effect(fn) { const _effect = new ReactiveEffect(fn); _effect.run(); }
delete obj.key,Proxy 可以arr[index] = val,需要特殊处理.value 访问,适合基本类型和需要替换整个对象的场景实际开发中,推荐用 ref 作为默认选择,因为更灵活。
nextTick 的实现:
Promise.thenMessageChannel → setTimeoutnextTick 回调会在 DOM 更新完成后执行。