We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
effect(fn, options)方法用来创建响应式函数,当fn执行的时候,会在代码中触发被观察对象的get,从而引起依赖收集,收集的对象就是我们当前调用的effect(fn, options)返回的effect函数,当被观察者对象被修改后,trigger负责再次触发effect重新执行,而在vue中,创建effect时候的fn往往会执行组件的render函数,得到新的vnode对象,然后再借助patch更新dom视图。 接下来看下具体的代码实现:
// 用来存放所有响应式对象的 key:被观察者 -> value:观察者 的存储仓库 // key只能是对象object value是一个Map结构 Map以用户要观察的对象的key作为key // 而对应的value则是一个 Set结构 存放在它发生改变后执行的多个 响应式函数 effect const targetMap = new WeakMap(); // effect在执行过程中 由于存在组件渲染 会再次生成render函数从而产生新的effect函数 并调用 由此形成effect栈 const effectStack = []; // 当前正在执行的effect函数 执行过程中 响应式被观察的变量触发get的时候 会把当前effect加入自己的观察者set中 称之为 依赖收集 let activeEffect; // 在依赖收集的过程中 一些方法触发了被观察对象的遍历操作 那么当对象发生改变的时候 这个遍历操作的结果也有可能被改变 因此 需要我们再次触发effect更新最新的结果 // 这个key值就是用来记录遍历操作的 const ITERATE_KEY = Symbol('iterate'); // 对于Map结构 独有的 .keys() 我们也收集有触发它的操作 当我们从map中移除或者添加新元素的时候 之前有依赖这个遍历key操作的结果 也会发生改变 我们就需要再次触发effect去更新结果 // 这个key值就是用来收集keys操作的 const MAP_KEY_ITERATE_KEY = Symbol('Map key iterate'); // 标识fn是否是effect函数 function isEffect(fn) { return fn && fn._isEffect === true; } // 外部调用入口 根据fn和配置项 返回一个effect函数 function effect(fn, options = EMPTY_OBJ) { if (isEffect(fn)) { fn = fn.raw; } // 内部调用 createReactiveEffect const effect = createReactiveEffect(fn, options); // 立即执行一次 触发依赖收集 if (!options.lazy) { effect(); } return effect; } // 停止一个effect 也就是当被观察对象改变后 也不再触发这个effect方法了 function stop(effect) { if (effect.active) { // 清理 cleanup(effect); // stop钩子 if (effect.options.onStop) { effect.options.onStop(); } effect.active = false; } } // 实际实现 let uid = 0; function createReactiveEffect(fn, options) { const effect = function reactiveEffect() { // scheduler 代表接受vue的队列调度 // 不存在 scheduler 意味着再次触发这个 effect 还可以执行 fn 但是由于effect已经从被观察者的set中被移除了 所以自动触发不会 只能手动触发了 if (!effect.active) { return options.scheduler ? undefined : fn(); } // 防止无休递归调用自己 if (!effectStack.includes(effect)) { // 每次执行之前 清理一次 cleanup(effect); try { // 开启track标志 可以触发依赖收集了接下来 enableTracking(); // 入栈 effectStack.push(effect); // activeEffect置位 可以被收集了 activeEffect = effect; // 执行 fn 其中的代码会触发被观察遍历的get操作 // 注意这个return的执行时机 // fn() 方法会执行 但是它的结果 并不马上通过return返回给effect的调用者 // 而是会在finally中的代码块执行结束后 看finally中有return回来的结果不 有的话用这个结果覆盖我们fn的结果 // 没有再直接返回fn的结果 // 这个顺序是真的容易搞错 return fn(); } finally { // 出栈 effectStack.pop(); // 恢复 resetTracking(); // 恢复上一个值 activeEffect = effectStack[effectStack.length - 1]; } } }; // id effect.id = uid++; // 允许递归调用自己 effect.allowRecurse = !!options.allowRecurse; effect._isEffect = true; effect.active = true; effect.raw = fn; // 当前effect被哪些 被观察者的set 所持有 方面在停止effect的时候 直接从set中移除effect effect.deps = []; effect.options = options; return effect; } // 清理effect 从被观察者的 观察者集合中移除这个观察者 function cleanup(effect) { const { deps } = effect; if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect); } deps.length = 0; } } // 开启 停止 依赖收集的功能标志位 let shouldTrack = true; // effect存在调用栈 因此 对应的这个也存在同样的场景 const trackStack = []; // 暂停 function pauseTracking() { trackStack.push(shouldTrack); shouldTrack = false; } // 开启 function enableTracking() { trackStack.push(shouldTrack); shouldTrack = true; } // 恢复 function resetTracking() { const last = trackStack.pop(); // 默认是true 同上面的初始化 如果是栈的最底层了 shouldTrack = last === undefined ? true : last; } // 每个被响应式对象的某个key的值或者自身被触发 get 然后在代理hanlder都存在的track追踪方法 function track(target, type, key) { // 关闭追踪 或者 无有效effect观察者函数 if (!shouldTrack || activeEffect === undefined) { return; } // 统一从 targetMap 根据待观察对象 target 取 map let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } // 根据 key 从map 中取 set let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } // 添加新的观察者到自己的set中 同时 观察者effect的deps被观察者集合也持有 当前set if (!dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps.push(dep); // onTrack 钩子 if (activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key }); } } } // 对应track 当我们改变了被观察者对象的某个key的值 或者自身的情况 handler对set做了拦截处理 其中就有trigger的调用 // 6个参数 前5个比较好理解 target比较好理解 被改变的对象 而trigger方法的触发其实在对target对象的修改生效之后 // 这意味着 如 map.clear() 这样的操作的话 target集合在此时已经是空的了 而 oldTarget则是我们拷贝的一份旧的集合数据 function trigger(target, type, key, newValue, oldValue, oldTarget) { // 取出map const depsMap = targetMap.get(target); if (!depsMap) { // never been tracked return; } // 所有待执行的effect集合 const effects = new Set(); // 以set集合为单位添加 const add = (effectsToAdd) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { // 非 activeEffect 或者 允许activeEffect递归调用自己 if (effect !== activeEffect || effect.allowRecurse) { effects.add(effect); } }); } }; // 清空类型的修改 那之前target对象对应的map中所有的key对应的set的观察者都需要更新 if (type === "clear" /* CLEAR */) { // collection being cleared // trigger all effects for target depsMap.forEach(add); } // 对于数组对象而言 长度类型的改变事件 else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { // 一个是我们原本额外 对数组对象的一些可以引起数组长度发生改变的操作 如push splice等 而设置的length类型事件 // 还有原本数组的key 索引在新的length newValue长度之后 意味着 有元素被删除了 if (key === 'length' || key >= newValue) { add(dep); } }); } else { // schedule runs for SET | ADD | DELETE // 修改 添加 删除 3类普通操作 if (key !== void 0) { add(depsMap.get(key)); } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case "add" /* ADD */: // map和set才有的add if (!isArray(target)) { // 触发 遍历事件的观察者 add(depsMap.get(ITERATE_KEY)); // 触发map独有的keys的观察者 if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); } } // 数组对象的add 我们触发长度事件的观察者 else if (isIntegerKey(key)) { // new index added to array -> length changes add(depsMap.get('length')); } break; case "delete" /* DELETE */: // map和set的delete if (!isArray(target)) { // 同上 add(depsMap.get(ITERATE_KEY)); // 同上 if (isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); } } break; case "set" /* SET */: // map独有的set if (isMap(target)) { add(depsMap.get(ITERATE_KEY)); } break; } } // 执行方法的实现 const run = (effect) => { // 配置项中的 onTrigger 钩子 if (effect.options.onTrigger) { effect.options.onTrigger({ effect, target, key, type, newValue, oldValue, oldTarget }); } // 接受vue队列调度的effect 按调度方法走 if (effect.options.scheduler) { effect.options.scheduler(effect); } // 否则就直接执行就好了 else { effect(); } }; // 逐一执行 effects.forEach(run); }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
effect(fn, options)方法用来创建响应式函数,当fn执行的时候,会在代码中触发被观察对象的get,从而引起依赖收集,收集的对象就是我们当前调用的effect(fn, options)返回的effect函数,当被观察者对象被修改后,trigger负责再次触发effect重新执行,而在vue中,创建effect时候的fn往往会执行组件的render函数,得到新的vnode对象,然后再借助patch更新dom视图。
接下来看下具体的代码实现:
The text was updated successfully, but these errors were encountered: