effect的基本实现
export let activeEffect = undefined;// 当前正在执行的effect class ReactiveEffect { ? ? active = true; ? ? deps = []; // 收集effect中使用到的属性 ? ? parent = undefined; ? ? constructor(public fn) { } ? ? run() { ? ? ? ? if (!this.active) { // 不是激活状态 ? ? ? ? ? ? return this.fn(); ? ? ? ? } ? ? ? ? try { ? ? ? ? ? ? this.parent = activeEffect; // 当前的effect就是他的父亲 ? ? ? ? ? ? activeEffect = this; // 设置成正在激活的是当前effect ? ? ? ? ? ? return this.fn(); ? ? ? ? } finally { ? ? ? ? ? ? activeEffect = this.parent; // 执行完毕后还原activeEffect ? ? ? ? ? ? this.parent = undefined; ? ? ? ? } ? ? } } export function effect(fn, options?) { ? ? const _effect = new ReactiveEffect(fn); // 创建响应式effect ? ? _effect.run(); // 让响应式effect默认执行 }
依赖收集
get(target, key, receiver) { ? ? if (key === ReactiveFlags.IS_REACTIVE) { ? ? ? ? return true; ? ? } ? ? const res = Reflect.get(target, key, receiver); ? ? track(target, 'get', key); ?// 依赖收集 ? ? return res; }
const targetMap = new WeakMap(); // 记录依赖关系 export function track(target, type, key) { ? ? if (activeEffect) { ? ? ? ? let depsMap = targetMap.get(target); // {对象:map} ? ? ? ? if (!depsMap) { ? ? ? ? ? ? targetMap.set(target, (depsMap = new Map())) ? ? ? ? } ? ? ? ? let dep = depsMap.get(key); ? ? ? ? if (!dep) { ? ? ? ? ? ? depsMap.set(key, (dep = new Set())) // {对象:{ 属性 :[ dep, dep ]}} ? ? ? ? } ? ? ? ? let shouldTrack = !dep.has(activeEffect) ? ? ? ? if (shouldTrack) { ? ? ? ? ? ? dep.add(activeEffect); ? ? ? ? ? ? activeEffect.deps.push(dep); // 让effect记住dep,这样后续可以用于清理 ? ? ? ? } ? ? } }
将属性和对应的effect维护成映射关系,后续属性变化可以触发对应的effect函数重新run
触发更新
set(target, key, value, receiver) { ? ? // 等会赋值的时候可以重新触发effect执行 ? ? let oldValue = target[key] ? ? const result = Reflect.set(target, key, value, receiver); ? ? if (oldValue !== value) { ? ? ? ? trigger(target, 'set', key, value, oldValue) ? ? } ? ? return result; }
export function trigger(target, type, key?, newValue?, oldValue?) { ? ? const depsMap = targetMap.get(target); // 获取对应的映射表 ? ? if (!depsMap) { ? ? ? ? return ? ? } ? ? const effects = depsMap.get(key); ? ? effects && effects.forEach(effect => { ? ? ? ? if (effect !== activeEffect) effect.run(); // 防止循环 ? ? }) }
分支切换与cleanup
在渲染时我们要避免副作用函数产生的遗留
const state = reactive({ flag: true, name: 'jw', age: 30 }) effect(() => { // 副作用函数 (effect执行渲染了页面) ? ? console.log('render') ? ? document.body.innerHTML = state.flag ? state.name : state.age }); setTimeout(() => { ? ? state.flag = false; ? ? setTimeout(() => { ? ? ? ? console.log('修改name,原则上不更新') ? ? ? ? state.name = 'zf' ? ? }, 1000); }, 1000)
function cleanupEffect(effect) { ? ? const { deps } = effect; // 清理effect ? ? for (let i = 0; i < deps.length; i++) { ? ? ? ? deps[i].delete(effect); ? ? } ? ? effect.deps.length = 0; } class ReactiveEffect { ? ? active = true; ? ? deps = []; // 收集effect中使用到的属性 ? ? parent = undefined; ? ? constructor(public fn) { } ? ? run() { ? ? ? ? try { ? ? ? ? ? ? this.parent = activeEffect; // 当前的effect就是他的父亲 ? ? ? ? ? ? activeEffect = this; // 设置成正在激活的是当前effect + ? ? ? ? ? cleanupEffect(this); ? ? ? ? ? ? return this.fn(); // 先清理在运行 ? ? ? ? } ? ? } }
这里要注意的是:触发时会进行清理操作(清理effect),在重新进行收集(收集effect)。在循环过程中会导致死循环。
let effect = () => {}; let s = new Set([effect]) s.forEach(item=>{s.delete(effect); s.add(effect)}); // 这样就导致死循环了
停止effect
export class ReactiveEffect { ? ? stop(){ ? ? ? ? if(this.active){? ? ? ? ? ? ? cleanupEffect(this); ? ? ? ? ? ? this.active = false ? ? ? ? } ? ? } } export function effect(fn, options?) { ? ? const _effect = new ReactiveEffect(fn);? ? ? _effect.run(); ? ? const runner = _effect.run.bind(_effect); ? ? runner.effect = _effect; ? ? return runner; // 返回runner }
调度执行
trigger触发时,我们可以自己决定副作用函数执行的时机、次数、及执行方式
export function effect(fn, options:any = {}) { ? ? const _effect = new ReactiveEffect(fn,options.scheduler); // 创建响应式effect ? ? // if(options){ ? ? // ? ? Object.assign(_effect,options); // 扩展属性 ? ? // } ? ? _effect.run(); // 让响应式effect默认执行 ? ? const runner = _effect.run.bind(_effect); ? ? runner.effect = _effect; ? ? return runner; // 返回runner } export function trigger(target, type, key?, newValue?, oldValue?) { ? ? const depsMap = targetMap.get(target); ? ? if (!depsMap) { ? ? ? ? return ? ? } ? ? let effects = depsMap.get(key); ? ? if (effects) { ? ? ? ? effects = new Set(effects); ? ? ? ? for (const effect of effects) { ? ? ? ? ? ? if (effect !== activeEffect) {? ? ? ? ? ? ? ? ? if(effect.scheduler){ // 如果有调度函数则执行调度函数 ? ? ? ? ? ? ? ? ? ? effect.scheduler() ? ? ? ? ? ? ? ? }else{ ? ? ? ? ? ? ? ? ? ? effect.run();? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? } }
深度代理
get(target, key, receiver) { ? ? if (key === ReactiveFlags.IS_REACTIVE) { ? ? ? ? return true; ? ? } ? ? // 等会谁来取值就做依赖收集 ? ? const res = Reflect.get(target, key, receiver); ? ? track(target, 'get', key); ? ? if(isObject(res)){ ? ? ? ? return reactive(res); ? ? } ? ? return res; }
当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理
总结
为了实现响应式,我们使用了new Proxy
effect默认数据变化要能更新,我们先将正在执行的effect作为全局变量,渲染(取值),然后在get方法中进行依赖收集
依赖收集的数据格式weakMap(对象:map(属性:set(effect))
用户数据发生变化,会通过对象属性来查找对应的effect集合,全部执行;
调度器的实现,创建effect时,把scheduler存在实例上,调用runner时,判断如果有调度器就调用调度器,否则执行runner
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
查看更多关于vue3中的响应式原理-effect的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://haodehen.cn/did122237