首先来了解2个全局常量和1个变量。
- targetMap [new WeakMap] : 在track()、trigger()的时候,冲当中间关联容器
- effectStack [Array] : 顾名思义就是存放effect的容器
- activeEffect : 在track()的时候作为Target的收集对象,可以当作一个临时作用的变量
1、reactive(target)
reactive实现原理,不废话(bb)直接上图
- 原理大致就是通过 new Proxy() 重写getter、setter方法来实现自己需要的逻辑
- 重写getter实现track收集effect
- 重写setter现实trigger触发effect
2、effect(fn,options)
effect是作为track()过程中最重要的环节,是唯一提供activeEffect的地方
3、computed(getterOrOptions)
通过上面流程图不难看出computed的实现本质是effect.
- 1.通过处理getterOrOptions参数得到一个匿名函数 getter
- 2.把getter作为 effect() 参数得到一个延迟执行的runner函数
- 3.把runner封装成一个 ref 对象并且返回。
4、watch(source,cb,options)
相比 computed ,watch的实现逻辑相对复杂一些。这里只讲解大致实现原理。通过分析源码发现,watch的大致原理也是建立在 effect 的实现上。
- 1.首先一些列处理把source 封装成为 effect的fn参数,例如:const getter = ()=> source;
- 2.通过effect得到一个延迟的runner函数
- 3.首次执行runner完成track()
- 4.返回一个停止监听的匿名函数
5、简单实现
重点说明: 以下代码( 省略了很多逻辑 )的实现仅供原理理解,具体实现请移步vue-next源码。
5.1、reactive
通过1、reactive(target)的分析和理解,reactive大致实现就是这样:
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
// getter
track(target, key); // 收集effect函数
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
// setter
let oldValue = target[key]; // 原始值
if (oldValue === value) {
return;
}
let res = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发effect函数
return res;
},
});
}
不了解Reflect的同学自行百度,这里不讲述。接下来实现track、trigger函数
//...
const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;
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()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
function trigger(target, key) {
// 触发
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
let dep = depsMap.get(key);
let effects = new Set();
dep && dep.forEach((effect) => effects.add(effect));
// 执行
effects.forEach((effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
});
}
上面就是整个reactive实现的大致代码。虽然不够完整,但是够理解了。想要深入了解的同学可以直接移步vue-next源码。
5.2、effect
接下来就是 effect(fn,options) 的实现。代码如下:
const effectStack = [];
let activeEffect = null;
//...
function effect(fn, options = {}) {
let effect = createEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect;
}
let uid = 0;
function createEffect(fn, options) {
const effect = () => {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeEffect = effect;
// 默认执行收集
return fn();
} finally {
// 处理完成
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.raw = fn;
effect.id = uid++;
effect.options = options;
//...
return effect;
}
通过 effect(fn,options) 可以分析出effect的执行返回的是fn()的结果。如果吧reactive、effect放在一起你会发现他们的依赖关系。Proxy 通过getter收集activeEffect、setter触发activeEffect。看似两个完成独立的方法,中间通过全局变量activeEffect联系在一起。
5.3、computed
reactive和effect的实现之后computed就相对简单多了。computed的实现代码:
//...
function computed(fn) {
let runner = effect(fn, {
lazy: true,
});
return {
get value() {
// 执行收集
return runner();
},
set value(value) {},
};
}
// 使用
let data = reactive({ count : 0 })
let text = computed(() => {
return `count:${data.count}`;
});
console.log(text.value) // count:0
对,没错。computed的实现就是这么简单。相比computed、watch的实现就复杂多了。
5.3、watch
// 这里只是简单的实现watch以供理解
function watch(source, cb) {
let getter = () => {};
if (isFunction(source)) {
getter = source;
}
// 收集信息
let runner = effect(getter, {
lazy: true,
scheduler: () => {
// 执行回调
let value = runner();
if (value !== oldValue) {
cb(oldValue, value);
}
oldValue = value;
},
});
// 第一次执行收集
let oldValue = runner();
//停止监听
return ()=>{
stop(runner)
//...
}
}
// 使用
let stopWatcher = watch(
() => data.count,
(oldValue, value) => {
// 执行
console.log("========watch========", oldValue, value);
}
);
// 可以停止watch
// stopWatcher()
data.count = 100;
// ========watch======== 0 100
5.4、完整的代码
// reactive
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
track(target, key);
// getter
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
let oldValue = target[key]; // 原始值
if (oldValue === value) {
return;
}
// setter
let res = Reflect.set(target, key, value, receiver);
trigger(target, key);
return res;
},
});
}
const targetMap = new WeakMap();
const effectStack = [];
let activeEffect = null;
// track
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()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
// trigger
function trigger(target, key) {
// 触发
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
let dep = depsMap.get(key);
let effects = new Set();
dep && dep.forEach((effect) => effects.add(effect));
// 执行
effects.forEach((effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
});
}
// effect
function effect(fn, options = {}) {
let effect = createEffect(fn, options);
if (!options.lazy) {
// 执行手机
effect();
}
return effect;
}
let uid = 0;
function createEffect(fn, options) {
const effect = () => {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeEffect = effect;
// 首次执行收集
return fn();
} finally {
// 处理完成
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.raw = fn;
effect.id = uid++;
effect.options = options;
return effect;
}
// computed
function computed(fn) {
let runner = effect(fn, {
lazy: true,
});
return {
get value() {
// 执行收集
return runner();
},
set value(value) {},
};
}
// watch
function watch(source, cb) {
let getter = () => {};
if (typeof source === "function") {
getter = source;
}
// 收集信息
let runner = effect(getter, {
lazy: true,
scheduler: () => {
// 执行回调
let value = runner();
if (value !== oldValue) {
cb(oldValue, value);
}
oldValue = value
},
});
// 第一次执行收集
let oldValue = runner();
}
6、总结
通过简单的实现可以总结出一下几点:
- vue核心是重写new Proxy()的getter、setter来实现数据驱动的核心
- computed和watch的实现可以看出实际原理都依赖effect()工厂
- activeEffect可以看作是new Proxy()和effec()的交通枢纽
再次声明:上述实现代码仅供理解实现原理,具体详细实现请移步vue-next源码。
萌新,如有错误欢迎指正 (ง •_•)ง