mini-vue深度剖析:从零构建Vue3核心响应式系统
你是否曾好奇Vue3的响应式系统是如何工作的?当数据变化时,视图如何自动更新?本文将带你从零开始,一步步剖析mini-vue中响应式系统的实现原理,让你彻底理解Vue3响应式的核心机制。读完本文,你将能够掌握响应式系统的设计思路、核心类与函数的实现,以及如何手动构建一个简单的响应式系统。
响应式系统核心概念
响应式系统是Vue3的核心特性之一,它允许我们声明式地将数据与视图绑定。当数据发生变化时,相关的视图会自动更新,而无需手动操作DOM。mini-vue作为Vue3的简化实现,其响应式系统包含以下几个核心部分:
- 依赖收集:当访问响应式数据时,收集当前的副作用函数(effect)。
- 依赖触发:当响应式数据发生变化时,触发之前收集的副作用函数,更新视图。
- 响应式对象:通过Proxy或Object.defineProperty将普通对象转换为响应式对象,拦截对象的读取和设置操作。
在mini-vue中,响应式系统的实现主要集中在packages/reactivity/src/目录下,相关的核心文件包括effect.ts、dep.ts、reactive.ts等。
ReactiveEffect类:副作用函数的封装
在响应式系统中,副作用函数(effect)是指会对响应式数据产生依赖,并在数据变化时需要重新执行的函数。mini-vue使用ReactiveEffect类来封装副作用函数,管理其生命周期和依赖关系。
ReactiveEffect类的核心属性与方法
ReactiveEffect类的定义位于effect.ts文件中,其主要属性和方法如下:
- active:标识副作用函数是否处于激活状态。
- deps:存储当前副作用函数所依赖的依赖集合(Dep)。
- run():执行副作用函数,并在执行期间开启依赖收集。
- stop():停止副作用函数,清除其依赖关系,使其不再响应数据变化。
以下是ReactiveEffect类的关键代码片段:
export class ReactiveEffect {
active = true;
deps = [];
public onStop?: () => void;
constructor(public fn, public scheduler?) {}
run() {
if (!this.active) {
return this.fn();
}
shouldTrack = true;
activeEffect = this as any;
const result = this.fn();
shouldTrack = false;
activeEffect = undefined;
return result;
}
stop() {
if (this.active) {
cleanupEffect(this);
if (this.onStop) {
this.onStop();
}
this.active = false;
}
}
}
在run()方法中,首先检查副作用函数是否处于激活状态。如果未激活,则直接执行函数并返回结果。如果激活,则开启依赖收集(shouldTrack = true),将当前ReactiveEffect实例设为全局活跃的副作用(activeEffect = this),然后执行副作用函数。函数执行完毕后,关闭依赖收集,重置全局活跃副作用。
stop()方法用于停止副作用函数。它会调用cleanupEffect函数清除当前副作用的所有依赖关系,并调用onStop回调函数(如果存在),最后将active属性设为false。
effect函数:创建副作用函数
为了方便用户创建副作用函数,mini-vue提供了effect函数。该函数接收一个副作用函数和可选的配置选项,创建ReactiveEffect实例并执行其run()方法,返回一个可执行的runner函数。
export function effect(fn, options = {}) {
const _effect = new ReactiveEffect(fn);
extend(_effect, options);
_effect.run();
const runner: any = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
通过effect函数创建的副作用函数会立即执行一次,之后当依赖的数据发生变化时,会自动重新执行。用户也可以通过返回的runner函数手动触发副作用函数的执行。
Dep类:依赖集合的管理
在响应式系统中,每个响应式数据的属性都对应一个依赖集合(Dep),用于存储依赖该属性的所有副作用函数。当属性的值发生变化时,会遍历依赖集合,触发所有副作用函数的执行。
Dep的创建与使用
在mini-vue中,Dep是通过createDep函数创建的,该函数定义在dep.ts文件中:
export function createDep(effects?) {
const dep = new Set(effects);
return dep;
}
可以看到,Dep本质上是一个Set集合,用于存储副作用函数(ReactiveEffect实例)。使用Set可以自动去重,避免重复收集相同的副作用函数。
在effect.ts文件中,提供了track和trigger两个函数,分别用于依赖收集和依赖触发。
track函数:收集依赖
track函数在读取响应式数据时被调用,用于收集当前活跃的副作用函数。其核心逻辑如下:
- 检查是否需要进行依赖收集(
shouldTrack为true且activeEffect存在)。 - 根据目标对象(target)从
targetMap中获取对应的depsMap(属性到依赖集合的映射)。 - 根据属性名(key)从
depsMap中获取对应的依赖集合(dep)。如果不存在,则创建一个新的Dep实例。 - 调用
trackEffects函数将当前活跃的副作用函数添加到依赖集合中。
export function track(target, type, key) {
if (!isTracking()) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = createDep();
depsMap.set(key, dep);
}
trackEffects(dep);
}
export function trackEffects(dep) {
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
(activeEffect as any).deps.push(dep);
}
}
targetMap是一个WeakMap,用于存储目标对象到depsMap的映射。depsMap是一个Map,用于存储属性名到依赖集合(Dep)的映射。这种数据结构可以高效地管理响应式数据与副作用函数之间的依赖关系。
trigger函数:触发依赖
trigger函数在修改响应式数据时被调用,用于触发依赖该数据的所有副作用函数。其核心逻辑如下:
- 根据目标对象(target)从
targetMap中获取对应的depsMap。 - 根据属性名(key)从
depsMap中获取对应的依赖集合(dep)。 - 调用
triggerEffects函数触发依赖集合中的所有副作用函数。
export function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
const effects: Array<any> = [];
deps.push(dep);
deps.forEach((dep) => {
effects.push(...dep);
});
triggerEffects(createDep(effects));
}
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
在triggerEffects函数中,如果副作用函数定义了scheduler(调度器),则调用scheduler函数,否则直接调用副作用函数的run方法。调度器可以用于控制副作用函数的执行时机,例如在Vue3中用于实现异步更新队列。
响应式系统的工作流程
结合ReactiveEffect、Dep以及track、trigger函数,mini-vue的响应式系统工作流程如下:
- 创建副作用函数:通过
effect函数创建ReactiveEffect实例,并执行副作用函数。 - 访问响应式数据:在副作用函数执行过程中,访问响应式数据的属性。
- 依赖收集:响应式数据的getter拦截器调用
track函数,将当前的activeEffect添加到该属性对应的依赖集合(Dep)中。 - 修改响应式数据:当响应式数据的属性被修改时,setter拦截器调用
trigger函数。 - 依赖触发:
trigger函数遍历该属性对应的依赖集合,执行所有副作用函数,更新视图。
下面是一个简单的示例,展示了响应式系统的工作过程:
import { reactive, effect } from 'mini-vue';
const state = reactive({ count: 0 });
let doubleCount;
effect(() => {
doubleCount = state.count * 2;
console.log('doubleCount:', doubleCount);
});
state.count = 1; // 输出: doubleCount: 2
state.count = 2; // 输出: doubleCount: 4
在这个示例中,当state.count被修改时,effect函数中定义的副作用函数会自动执行,更新doubleCount的值并打印。
总结与展望
本文详细剖析了mini-vue中响应式系统的核心实现,包括ReactiveEffect类对副作用函数的封装、Dep类对依赖集合的管理,以及track和trigger函数实现的依赖收集与触发机制。通过这些组件的协同工作,mini-vue实现了与Vue3类似的响应式功能。
当然,mini-vue的响应式系统还包括对ref、computed等API的支持,这些内容我们将在后续文章中进一步探讨。通过学习mini-vue的源码,我们可以更深入地理解Vue3的内部工作原理,为日常开发和问题排查提供有力的支持。
如果你对mini-vue的响应式系统感兴趣,可以通过以下步骤获取源码并进行深入学习:
- 克隆仓库:
git clone https://gitcode.com/gh_mirrors/mi/mini-vue - 进入项目目录:
cd gh_mirrors/mi/mini-vue - 安装依赖:
pnpm install - 运行测试:
pnpm test
希望本文能够帮助你更好地理解Vue3响应式系统的实现原理,祝你学习愉快!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



