实现响应式系统 3
依赖收集
实现 Effect
这里直接给出 Effect 实现:
/**
* 用于记录当前活动的 effect
*/
export let activeEffect = undefined;
export const targetMap = new WeakMap(); // 用来存储对象和其属性的依赖关系
const effectStack = [];
/**
* 该函数的作用,是执行传入的函数,并且在执行的过程中,收集依赖
* @param {*} fn 要执行的函数
*/
export function effect(fn) {
const environment = () => {
try {
activeEffect = environment;
effectStack.push(environment);
cleanup(environment);
return fn();
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
};
environment.deps = [];
environment();
}
export function cleanup(environment) {
let deps = environment.deps; // 拿到当前环境函数的依赖(是个数组)
if (deps.length) {
deps.forEach((dep) => {
dep.delete(environment);
});
deps.length = 0;
}
}
改造 track
之前 track 仅仅只是简单的打印,那么现在就不能是简单打印了,而是进行具体的依赖收集。
注意依赖收集的时候,需要按照上面的设计一层一层进行查找。
// 收集器:用于在获取属性、是否存在某个属性、便利属性操作是进行依赖收集
import { targetMap, activeEffect } from "./effect.js";
import { TrackOpTypes, TERATE_KEY } from "../utils.js";
// 控制是否需要进行依赖收集的开关
let shouldTrack = true;
/**
* 恢复依赖收集
*/
export function enableTracking() {
shouldTrack = true;
}
/**
* 暂停依赖收集
*/
export function disableTracking() {
shouldTrack = false;
}
/**
* 收集器用于收集依赖
* @param {Object} target 原始对象
* @param {String} type 操作类型:get/set...
* @param {String} key 属性
*/
export default function (target, type, key) {
// 先进行开关状态的判断
if (!shouldTrack) {
return;
}
// 依赖收集
// 这里要做的事情很简单就是一层一层的去找,找到了就存储
let propMap = targetMap.get(target);
if (!propMap) {
propMap = new Map();
target.set(target, propMap);
}
// 如果是遍历操作,key值是undefined,所以需要进行参数归一化
if (type === TrackOpTypes.ITERATE) {
key = TERATE_KEY;
}
let typeMap = propMap.get(key);
if (!typeMap) {
typeMap = new Map();
propMap.set(key, typeMap);
}
// 根据type值找对应的Set
let depSet = typeMap.get(type);
if (!depSet) {
depSet = new Set();
typeMap.set(type, depSet);
}
// 找到集合了,可以存储依赖了
if (!depSet.has(activeEffect)) {
depSet.add(activeEffect);
activeEffect.deps.push(depSet); // 将集合存储到deps数组里面
}
}
改造 trigger
trigger 要做的事情也很简单,就是从我们所设计的数据结构里面,一层一层去找,找到对应的依赖函数集合,然后全部执行一次。
首先我们需要建立一个设置行为和读取行为之间的映射关系:
// 定义修改数据和触发数据的映射关系
const triggerTypeMap = {
[TriggerOpTypes.SET]: [TrackOpTypes.GET],
[TriggerOpTypes.ADD]: [
TrackOpTypes.GET,
TrackOpTypes.ITERATE,
TrackOpTypes.HAS,
],
[TriggerOpTypes.DELETE]: [
TrackOpTypes.GET,
TrackOpTypes.ITERATE,
TrackOpTypes.HAS,
],
};
我们前面在建立映射关系的时候,是根据具体的获取信息的行为来建立的映射关系,那么我们获取信息的行为有:
- GET
- HAS
- ITERATE
这些都是在获取成员信息,而依赖函数就是和这些获取信息的行为进行映射的。
因此在进行设置操作的时候,需要思考一下当前的设置,会涉及到哪些获取成员的行为,然后才能找出该行为所对应的依赖函数。
// trigger.js
// 触发器:用于在设置属性、新增属性、删除属性操作时进行派发更新
import { TriggerOpTypes, TrackOpTypes, ITERATE_KEY } from "../utils.js";
import { targetMap, activeEffect } from "./effect.js";
// 定义修改数据和触发数据的映射关系
const triggerTypeMap = {
[TriggerOpTypes.SET]: [TrackOpTypes.GET],
[TriggerOpTypes.ADD]: [
TrackOpTypes.GET,
TrackOpTypes.ITERATE,
TrackOpTypes.HAS,
],
[TriggerOpTypes.DELETE]: [
TrackOpTypes.GET,
TrackOpTypes.ITERATE,
TrackOpTypes.HAS,
],
};
/**
* 触发器
* @param {Object} target 原始对象
* @param {String} type 操作的类型
* @param {String} key 属性
*/
export default function (target, type, key) {
// 要做的事情很简单,就是找到依赖然后执行依赖
const effectFns = getEffectFns(target, type, key);
if (!effectFns) return;
for (let effectFn of effectFns) {
if (effectFn !== activeEffect) {
effectFn();
}
}
}
// 根据传入的信息,找到对应的依赖集合
function getEffectFns(target, type, key) {
const propMap = targetMap.get(target);
if (!propMap) return;
// 如果是新增或者删除操作会额外触发迭代
const keys = [key];
if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
keys.push(ITERATE_KEY);
}
const effectFns = new Set(); // 用于存储依赖的集合
for (const key of keys) {
const typeMap = propMap.get(key);
if (!typeMap) continue;
const trackTypes = triggerTypeMap[type];
for (const trackType of trackTypes) {
const dep = typeMap.get(trackType);
if (!dep) continue;
for (const effectFn of dep) {
effectFns.add(effectFn);
}
}
}
return effectFns;
}
const obj = {
a: 1,
b: 2,
};
const state = reactive(obj);
// 测试1
function fn() {
console.log("fn");
state.a = state.a + 1;
}
effect(fn);
state.a = 100;
// 测试2
effect(() => {
if (state.a === 1) {
state.b;
} else {
state.c;
}
console.log("执行了函数1");
});
effect(() => {
console.log(state.c);
console.log("执行了函数2");
});
state.a = 2;
state.c = 2;
懒执行
有些时候我们想要实现懒执行,也就是不想要传入 effect 的回调函数自动就执行一次,通过配置项来实现
export function effect(fn, options = {}) {
const { lazy = false } = options;
const environment = () => {
try {
activeEffect = environment;
effectStack.push(environment);
cleanup(environment);
return fn();
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
};
environment.deps = [];
if (!lazy) {
environment();
}
return environment;
}
添加回调
有些时候需要由用户来指定是否派发更新,支持用户传入一个回调函数,然后将要依赖的函数作为参数传递回给用户给的回调函数,由用户来决定如何处理。
// effect.js
export function effect(fn, options = {}) {
const { lazy = false } = options;
const environment = () => {
try {
activeEffect = environment;
effectStack.push(environment);
cleanup(environment);
return fn();
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
};
environment.deps = [];
environment.options = options; // 传递回调函数
if (!lazy) {
environment();
}
return environment;
}
// trigger.js
export default function (target, type, key) {
// 要做的事情很简单,就是找到依赖然后执行依赖
const effectFns = getEffectFns(target, type, key);
if (!effectFns) return;
for (const effectFn of effectFns) {
if (effectFn === activeEffect) continue;
if (effectFn.options && effectFn.options.scheduler) {
// 说明用户传递了回调函数,用户期望自己来处理依赖的函数
effectFn.options.scheduler(effectFn);
} else {
// 否则就直接执行依赖函数
effectFn();
}
}
}