effect
- 其实想要渲染的话就只要执行 effect 中的回调函数就行了.
- 要是那么简单就好了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue3</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { reactive, effect } from "./reactivity.js";
const data = { name: "aa", age: "bb" };
const state = reactive(data);
effect(() => {
app.innerHTML = state.name + state.age;
});
</script>
</body>
</html>
reactive/src/effect.ts
class ReactiveEffect {
fn;
constructor(fn) {
this.fn = fn;
}
run() {
this.fn();
}
}
export function effect(fn, options) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}
然后在 reactive/src/index.ts 中导入
...
export * from "./effect";
依赖收集
reactivity/src/effect.ts
- 依赖收集的数据结构 Map{ target: Map{ key: Set}}
- 在每一次收集依赖之前都要清理,例子就是 那种有 v-if 的那种结构,如果不清理,下一次可能还在依赖中
let activeEffect;
const targetMap = new WeakMap();
class ReactiveEffect {
fn;
parent;
deps = [];
constructor(fn, public scheduler?) {
this.fn = fn;
}
run() {
// 这里就是考虑effect 套effect的这种情况
try {
this.parent = activeEffect;
// 把当前的ReactiveEffect放到了全局上
activeEffect = this;
cleanupEffect(this);
return this.fn();
} finally {
// 这里的finally的执行时机就是fn执行完了
activeEffect = this.parent;
this.parent = undefined;
}
}
}
// 清理 每次都要重新收集
function cleanupEffect(effect) {
const { deps } = effect;
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
}
deps.length = 0;
}
// 结构就是这样
// {name: 'aa', age: 20}: Map({ name: Set()})
export function track(target, key) {
if (activeEffect) {
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()));
}
trackEffects(dep);
}
}
function trackEffects(dep) {
let shouldTrack = false;
// 源码中这里用了很多的位运算 暂时不考虑
shouldTrack = !dep.has(activeEffect!);
if (shouldTrack) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
export function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) {
// 没有被追踪过
return;
}
let effects = depsMap.get(key);
triggerEffects(effects);
}
// 其实就是拿所有的dep执行
export function triggerEffects(dep) {
const effects = [...dep];
for (const effect of effects) {
if (effect.scheduler) {
effect.scheduler();
} else {
effect.run();
}
}
}
export function effect(fn, options?) {
const _effect = new ReactiveEffect(fn, options.scheduler);
if (!options || !options.lazy) {
_effect.run();
}
const runner = _effect.run.bind(_effect);
return runner;
}
reactivity/src/baseHandlers.ts
import { track, trigger } from "./effect";
export const MutableReactiveHandler = {
get(target, key, receiver) {
...
track(target, key)
...
},
set(target, key, value, receiver) {
let oldValue = target[key]
const result = Reflect.set(target, key, value, receiver);
if (hasChanged(value, oldValue)) {
trigger(target, key)
}
return result;
},
};