<
>

vue3中的响应式原理-effect

2022-08-12 18:11:47 来源:易采站长站 作者:

目录effect的基本实现依赖收集触发更新分支切换与cleanup停止effect调度执行深度代理总结effect的基本实现exportletactiveEffect=undefined;//...

目录
effect的基本实现
依赖收集
触发更新
分支切换与cleanup
停止effect
调度执行
深度代理 
总结

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(() => php{
    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 ReacjstiveEffect {
  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

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

暂时禁止评论

微信扫一扫

易采站长站微信账号