今天继续总结学习Vue3.0的基本原理。effect(),意为副作用,此方法默认执行一次,如果数据变化后会触发里边的回调函数。
在进入effect后首先把effect包装成响应式的effect,并且为了后边的使用会通过配置参数对其包装。
export
function
effect(fn, options = {}) {
const effect = creatReactiveEffect(fn, options);
if (!options.lazy) { //根据options的属性后续会对effect变形,watchEffect
effect();
}
return effect;
}
为了在访问修改数据时对数据进行依赖收集与触发,所以在执行用户传入的fn函数时要创建变量为对依赖的处理时做服务。
//创建响应式的effect 返回一个函数
let uid = 0;//记录每个effect的标识
let activeEffect; //传递信息的effect替代品
const effectStack = [];//存储effect的栈
function
creatReactiveEffect(fn,options) {
const effect = function
reactiveEffect() {
if (!effectStack.includes(effect)) { //effectStack栈不允许重复 避免死循环
try {
effectStack.push(effect);//将当前的effect放到effectStack中
activeEffect = effect;//将当前的effect赋给activeEffect
return fn();//执行用户传入的函数
} finally {
effectStack.pop();//清空栈
activeEffect = effectStack[effectStack.length - 1];//清除activeEffect
}
}
}
effect.options = options;
effect.id = uid ;
effect.deps = [];//此次effect依赖了哪些属性
return effect;
}
创建的变量会在后续中使用到。在创建响应式effect时主要是执行用户的fn,并且将fn用到的依赖收集起来,以便更改数据时找到对象对应的effect,再次处理回调fn; 在收集依赖时,采用weakMap的数据结构来存储对象属性所对应的effect;
WeakMap
{
{"name":"zs","age":15}:Map{"name":Set{effect1,effect2},"age":{effect}}
}
所以在收集与触发依赖时要从这个结构出发:
const targetMap = new
WeakMap();
//依赖收集
export
function
track(target, type, key) {
if (activeEffect == undefined) {
return;// 说明取值的属性不依赖于effect 不做事
}
let depsMap = targetMap.get(target); //获取weakMap中对象对应的Map
if (!depsMap) { //如果没有对应的map就构建一个
targetMap.set(target,(depsMap = new
Map()))
}
let dep = depsMap.get(key);//去map里去取装有effect的set集合
if (!dep) { //取不到就构建一个
depsMap.set(key,(dep = new
Set()))
}
if (!dep.has(activeEffect)) { //如果原来的集合里没有此属性对应的effect那就加进去
dep.add(activeEffect)
}
}
收集依赖的函数已经写好,所以我们要在数据代理时去收集依赖:
function
createGetter() {
return
function
get(target, key, receiver) { // proxy reflect
const res = Reflect.get(target, key, receiver); // target[key];
// todo..
// console.log('用户对这个对象取值了',target,key);
track(target, TrackOpTypes.GET, key); // 依赖收集
if (isObject(res)) {
return reactive(res)
}
return res
}
}
TrackOpTypes.GET以及后边的TriggerOpTypes.SET、TriggerOpTypes.ADD都是定义的常量; 依赖收集好以后需要在改变数据的时候去找到依赖并且执行依赖对应的effect:
//触发effect
export
function
trigger(target, type, key, value, oldValue) {
//获取当前对应的map
const depsMap = targetMap.get(target);
if (!depsMap) {
return; //说明没有被收集过依赖
}
//具体执行effect集合的方法
const run = (effect) => {
if (effect) {
effect.forEach(effect => effect())
}
}
if (key !== null) { //有对象的键 就执行对应的装有effect的set集合里的每个effect
run(depsMap.get(key));
}
if (type === TriggerOpTypes.ADD) { //如果是数组新增了 那就去触发length属性所对应的依赖 取length属性对应的effect集合
run(depsMap.get(Array.isArray(target) ? 'length' : ''))
}
}
特别的,对于数组来说新增元素时会根据key去找依赖,但是此时对于新增的元素来说根本没有被收集过依赖,所以就找不到对用的effect,所以,对于新增元素来说,要去找length对应的依赖。 现在需要在修改属性时来触发effect:
function
createSetter() {
return
function
set(target, key, value, receiver) {
// 需要判断是修改属性 还是增加属性 ,如果原来的值 和新设置的值一样什么都不做
const hadKey = hasOwn(target, key);
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver); // target[key] = value
// todo...
if (!hadKey) {
// console.log('属性的新增操作', target, key);
trigger(target, TriggerOpTypes.ADD, key, value);
} else
if (hasChanged(value, oldValue)) {
// console.log('修改操作',target,key);
trigger(target, TriggerOpTypes.SET, key, value, oldValue); // 触发依赖更新
}
// 值没有变化什么都不用做
return result;
}
}
关于基本的effect原理就实现了,后续的计算属性等还会在此基础上增加操作。