响应式
对数据的操作进行拦截处理。当访问它时就对当前触发它的渲染副作用函数进行收集,这样之后在更改时就可以通知对应的副作用函数进行更新。进而实现了响应式的效果
reactive 函数
- reactive()-> createReactiveObject() 对 target 进行 new Proxy(target, mutableHandlers) 拦截并保存结果 p 防止重复响应式化。
- mutableHandlers{} 主要对 get/set/deleteProperty/has/ownKeys 操作进行拦截
- getter() -> createGetter() 配合 const res = Reflect.get(target, key, receiver) 获取值,并执行对当前 key 的依赖收集 track()。如果返回的 res 也是对象,则继续递归进行响应式化。
- 区别:与vue2不同的是 reactive() 一开始只对对象本身进行 proxy 处理,而子层只有在 getter 获取时,判断为对象再进行递归处理。这样降低了消耗和对无效数据的响应式处理
- setter() -> createSetter() 配合 const result = Reflect.set(target, key, value, receiver) 并触发当前 key 的派发通知 trigger()。
- getter() -> createGetter() 配合 const res = Reflect.get(target, key, receiver) 获取值,并执行对当前 key 的依赖收集 track()。如果返回的 res 也是对象,则继续递归进行响应式化。
- track() 收集的是响应式 effect() 副作用函数(vue2 收集的是 watcher{})。全局 targetMap{} 存储着每个 target 对应的 depsMap{},depsMap{} 内为该 target 的 key 对应的 dep[ ...effect() ] 集合。同时当前的 activeEffect=effect() 也会双向收集 dep[],用于后续的 cleanUp() 清理
- 组件初始化时 setupRenderEffect() 设置并执行 effect() -> renderComponentRoot() 创建 subtree 会去读对应的 data 进而完成当前的 effect() 被收集
- 结构关系:targetMap: { target: { key: [ effect() ] } };targetMap 为 WeakMap 类型
- trigger() 派发时通过 target->key->dep[] 获取并创建个新的 effects[] 用于存储待执行的 effect()
- effect() 副作用函数 -> createReactiveEffect() 是对当前待执行渲染函数进行包裹,内部保存了这个 fn() 并定义其他属性。当需要执行时配合 effectStack[] 调用栈、activeEffect 指针实现树深度遍历式的触发顺序,并完成当前 effeect() 被收集的动作
- cleanup() 在执行时先清除收集了该 effect() 的 dep,之后实际运行时在重新收集。防止已经失效的 dep 对 effect() 的重新触发
ref 函数
- ref() -> createRef() -> new RefImpl() 生成 ref{}
- class RefImpl{} 类内部定义了对 value 属性且对 getter/setter 访问拦截。这就是为什么需要以 ref.value 形式读取/修改了。且 getter/setter 拦截对应着 track/trigger()
- track() 收集的 target 是生成的 new RefImpl() 对象本身
- 注意:当创建的 value 是对象/数组类型,则调用的 reactive()
计算属性 computed
对配置的 getter() 进行延迟处理,及根据依赖的变动再重新计算获取新值
- computed() -> new ComputedRefImpl() 生成 computed{},对参数做标准化处理
- class ComputedRefImpl{} 执行时对 getter() 进行 effect 包裹并设置 { lazy, scheduler } 所以这个 effect() 非立即执行,之后当读取该属性时才真正执行 effect() -> getter() 获取返回值同时保存它。然后在执行内部 track() 收集依赖
- 依赖收集流程:因为模版先读取 computed 而后 computed 触发 getter(),然后 computed 再执行自身的 track(),所以 computed 收集的是 render.effect() 而依赖收集的 computed.effect()
- 更新流程:修改依赖 data 它通知 computed.effect(),然后 computed 再通知 render.effect() 读取新值
- 区别:在 vue2 内 data 是同时收集的 computed.watcher{} 和 render.watcher{},然后修改时先告诉 computed 重新计算,再触发 render()
侦听器 watch
对 getter() 进行标准化处理,对 cb 进行 scheduler 化并按 flush 放入不同的执行队列中
- watch/watchEffect() -> doWatch() 主要实习内容
- getter() 会对传入的数据进行标准化处理
- 构造 job=applyCb() -> cb() 回调,实现 watch(newVal, oldVal, onCleanup) 传递参数
- 对 cb() 进行 scheduler 化处理。按配置的 flush 方式,将 cb() 放入不同的异步队列内 pendingPreFlushCbs/pendingPostFlushCbs[]。 而 sync 则不进入异步队列直接是 scheduler=job
- 所以非 sync 时多次修改响应式数据在一次 tick 内 cb() 只触发一次
- 创建 effect() -> getter() 会在 job() 内被执行
- 在 flushJobs() 内实际触发等待队列内的 cb()
- 在组件更新前执行 flushPreFlushCbs() -> pendingPreFlushCbs[],在更新后执行 flushPostFlushCbs() -> pendingPostFlushCbs[]
- 区别:3.x 同一个 getter 不再支持配置多个 cb(),需要添加多个 wetch() 实现
简单实现
响应式
- 依赖收集的层位变为 WeakMap -> Map -> Set
- wm 对应整个对象的 new Observe
- map 是整个 data 和 key 之间的关联桥梁
- set 是对应 key 的收集 dep
let activeEffect = null;
const effectStack = [];
function effect(fn) {
const effectFn = () => {
// 清除旧的依赖
cleanup(effectFn);
effectStack.push(effectFn);
activeEffect = effectFn;
// activeEffect = effectFn;
fn();
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
};
effectFn.deps = [];
effectFn();
}
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
// 清除收集了 effectFn 的 deps
const deps = effectFn.deps[i];
deps.delete(effectFn);
}
// 清除 effectFn 收集的 deps
effectFn.deps.length = 0;
}
// 层级关系
// wm -> map -> set
const reactiveMap = new WeakMap();
const obj = new Proxy(data, {
get(target, key) {
// map
let depsMap = reactiveMap.get(target);
if (!depsMap) {
reactiveMap.set(target, (depsMap = new Map()));
}
// set
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 收集依赖
deps.add(activeEffect);
// 相互收集依赖
activeEffect.deps.push(deps);
return target[key];
},
set(target, key, newValue) {
target[key] = newValue;
const depsMap = reactiveMap.get(target);
if (!depsMap) return;
// 获取对应的 set
const effects = depsMap.get(key);
// console.log('## set effects', effects);
// 通知该 deps 下的程序
// effects && effects.forEach((fn) => fn());
// 生成新的 effects 对象用于执行函数与原有 effects 脱离,防止死循环
// 不该动会先 del 再 add 这样循环的,因为都操作的一个 effects 对象
const effectsToRun = new Set(effects);
effectsToRun.forEach((fn) => fn());
}
});
computed
- 同样通过 dirty/lazy 来控制 computed 的执行时机
const data = {
a: 1,
b: 2
};
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i];
deps.delete(effectFn);
}
effectFn.deps.length = 0;
}
function pushTarget(effectFn) {
effectStack.push(effectFn);
activeEffect = effectFn;
}
function popTarget() {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
let activeEffect;
const effectStack = [];
function effect(fn, options = {}) {
const effectFn = () => {
cleanup(effectFn);
pushTarget(effectFn);
// 获取返回值
const res = fn();
popTarget();
return res;
};
effectFn.deps = [];
options.options = options;
if (!options.lazy) {
effectFn();
}
// 让执行动作由外部控制
return effectFn;
}
// 计算属性主体。读取方式 xxx.value
// 让 computed 也变为计算属性
function computed(fn) {
// value 作为缓存
let value;
let dirty = true;
const effectFn = effect(fn, {
lazy: true,
scheduler(fn) {
// 自己来触发自己的
// 更改 drity 状态,后续 xxx.value 时获取最新结果
dirty = true;
// 添加响应式 触发
trigger(obj, 'value');
}
});
// 创建一个 value 的拦截函数
// 最后这个 obj 也成为响应式
const obj = {
get value() {
if (dirty) {
value = effectFn();
dirty = false;
}
// 添加响应式 收集
track(obj, 'value');
return value;
}
};
// 通过 xxx.value 获取内容
return obj;
}
const obj = new Proxy(data, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
trigger(target, key);
}
});
// 响应式数据记录
const reactiveMap = new WeakMap();
function track(target, key) {
let depsMap = reactiveMap.get(target);
if (!depsMap) {
reactiveMap.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
deps.set(key, (deps = new Set()));
}
deps.add(activeEffect);
activeEffect.deps.push(deps);
}
function trigger(target, key) {
const depsMap = reactiveMap.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
const effectsToRun = new Set(effects);
// effectsToRun.forEach((fn) => fn());
effectsToRun.forEach((effectFn) => {
// 让 scheduler 回调函数去执行 effect
// 既 data 更新时并不直接执行 effect,而是修改 c-w.dirty 然后让 r-w 去获取新的
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn);
} else {
effectFn();
}
});
}