reecho
Version:
Reecho 是一款基于依赖收集的MVVM框架,它具有以下特点 - 声明式数据: 基于proxy的依赖收集 - 使用函数定义组件,但组件不会如React一样重复执行造成心智负担 - 读写分离,读取状态和更改状态统一使用函数,避免vue3的ref一样有时需要`xxx.value`有时不需要的不一致性 - 使用TS编写,类型友好
155 lines (138 loc) • 3.85 kB
text/typescript
/*
effect:只有在effect函数中的响应式属性才会被收集依赖
track:track函数会让当前属性收集effect
trigger:找到属性对应的effect,并执行
*/
import { isArray } from "../shared";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
// Dep为effect列表
type Dep = Set<ReactiveEffect>;
// key -> effect列表的映射关系
type KeyToDepMap = Map<any, Dep>;
export interface ReactiveEffect<T = any> {
(): T;
_isEffect: boolean;
id: number;
active: boolean;
// 存储effect的第一个函数参数
raw: () => T;
// 依赖列表
deps: Array<Dep>;
options: ReactiveEffectOptions;
}
export interface ReactiveEffectOptions {
// 是否在调用时自动执行参数函数
lazy?: boolean;
// 调度器,控制effct会的执行时机
scheduler?: (job: ReactiveEffect) => void;
}
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = {}
): ReactiveEffect<T> {
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect();
}
return effect;
}
// 记录当前的effect
let activeEffect;
let uid = 0;
// 存储effect的栈结构,用来处理effect多层嵌套调用的情况
const effectStack = [];
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function () {
// 控制effectStack中没有当前effect才会执行,防止effect递归执行变成死循环
if (!effectStack.includes(effect)) {
try {
activeEffect = effect;
effectStack.push(activeEffect);
return fn();
} finally {
// 保证activeEffect在嵌套调用时永远指向栈顶
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++;
effect.deps = []; //effect的依赖列表
effect.options = options;
effect.raw = fn;
effect._isEffect = true;
effect.active = true;
return effect;
}
// !!!将属性和effect双向关联!!!
// track的目的是形成类似{target: {key: [effect1, effect2]}} 的结构
let targetMap = new WeakMap<any, KeyToDepMap>();
// 收集依赖 在getter时调用
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (activeEffect == undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Set();
depsMap.set(key, dep);
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep); //双向记忆,构建activeEffect的依赖数组
}
}
// 触发更新 在setter时调用
// 执行副作用
const runEach = (effect: ReactiveEffect) => {
// 对于有调度器的effect,优先执行调度器函数
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else {
effect();
}
};
// 执行副作用队列
const run = (effects: Dep) => {
if (effects) {
effects.forEach((effect) => {
runEach(effect);
});
}
};
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
value?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
if (key === "length" && isArray(target)) {
// 处理直接修改数组length的情况 ex: a = [1,2] a.length = 5, 此时key为'length'
depsMap.forEach((dep, key) => {
if (key === "length" || key >= value) {
run(dep);
}
});
} else {
// 对象/数组正常key的处理
if (key != undefined) {
// 说明修改了key
run(depsMap.get(key));
}
// TODO 处理操作数组本不存在的key的情况 ex: a = [1,2,3] a[5] = 5
}
}