vue3源码(二)reactive&effect

本文详细介绍了Vue3中的reactive和effect功能,包括如何创建响应式对象,依赖收集机制,以及如何实现effect函数进行副作用管理。内容涵盖了响应式数据的创建、重复处理、依赖跟踪、effect的执行、清除和失活,以及调度执行策略。
摘要由CSDN通过智能技术生成

一.reactive与effect功能

reactive方法会将对象变成proxy对象, effect中使用reactive对象时会进行依赖收集,稍后属性变化时会重新执行effect函数。

<div id="app"></div>
    <script type="module">
      import {
     	reactive,
        effect,
       } from "/node_modules/@vue/reactivity/dist/reactivity.esm-browser.js";
    //   reactive创建一个响应式对象
    //   effect 副作用函数,默认执行一次,数据变化后再次执行
      const state = reactive({ name: "orange", age: 18 });
       effect(() => {
        document.getElementById("app").innerHTML = state.name;
      });
      console.log(state);
      setTimeout(() => {
        state.name = "apple";
      }, 2000);
    </script>

二.reactive与effect实现

1.实现reactive

1.1 get、set基础实现

// reactive.ts
import { isObject } from "@vue/shared";

const mutableHandlers = {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver);
  },
};

export function reactive(target) {
  if (!isObject(target)) {
    return target;
  }
  const proxy = new Proxy(target, mutableHandlers);
  return proxy;
}

如下图,如果采用target[key]方法,获取aliasName时 不会触发nameget
在这里插入图片描述

1.2 完善重复

const state = { name: "orange", age: 18 };
      const p1 = reactive(state);
      const p2 = reactive(state);
      const p3 = reactive(p1);
      console.log(p1 === p2);
      console.log(p1 === p3);
  • 响应式数据缓存,防止重复代理
    使用mapkey为对象,值为响应式对象,实现p1 === p2
// 2.1用于记录缓存代理 解决针对同一对象创建两次不相同问题
const reactiveMap = new WeakMap(); //内存泄漏
export function reactive(target) {
  if (!isObject(target)) {
    return target;
  }
  // 2.3如果在map中发现了相同的对象,则直接返回
  const exitProxy = reactiveMap.get(target);
  if (exitProxy) {
    return exitProxy;
  }
  const proxy = new Proxy(target, mutableHandlers);
  // 2.2创建过一次后,就放到map里面,属性名用对象实体
  reactiveMap.set(target, proxy);
  return proxy;
}
  • 响应式数据标记
    用枚举做标记,响应式对象都有get和set方法,p1初次创建时state没有getset方法,target[ReactiveFlags.IS_REACTIVE]取值为false,创建p3时,p1响应式,取值为true,直接返回p1
// 3.1 给对象增加标识,是否为代理对象
export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
}

get(target, key, receiver) {
 //  4.2 如果访问的是ReactiveFlags.IS_REACTIVE 就返回true,代表这个对象是被代理过的
    if (ReactiveFlags.IS_REACTIVE == key) {
      return true;
    }
    return Reflect.get(target, key, receiver);
  },

export function reactive(target) {
  ...,
  // 3.2 如果传递过来的对象已经是一个被代理过的对象,进行处理 直接返回这个对象
 //  4.1首先访问这个属性,如果是被代理过的对象,就会触发劫持的get方法
  if (target[ReactiveFlags.IS_REACTIVE]) {
    return target;
  }
  const proxy = new Proxy(target, mutableHandlers);
  reactiveMap.set(target, proxy);
  return proxy;
}

2.实现effect

2.1 effect函数

vue2vue3早期的依赖收集采用的都是栈方式存储,vue3后来改为树型数据存储。
effect执行时,把当前effect作为全局的,触发属性的get方法,收集依赖

let activeEffect;
class ReactiveEffect {
  _trackId = 0; //用于记录当前effect执行了几次
  public deps: Array<any> = []; // 判断依赖属性
  public _depsLength = 0;
  public active: boolean = true; // 是否是响应式的
  public parent = undefined;
  constructor(public fn, private scheduler) {}
  run() {
    if (!this.active) {
      return this.fn();
    }
    try {
      this.parent = activeEffect;
      activeEffect = this;
      preCleanEffect(this); // 清理只是更新长度。没有实质性的删除内容
      return this.fn();
    } finally {
    	postCleanEffect(this); // 对比后删除多余的内容
      activeEffect = this.parent;
      this.parent = undefined;
    }
  }
}
export function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

2.2 依赖收集

执行effect时,会触发依赖属性的get方法,在属性的get中进行依赖收集

get(target, key, receiver) {
    if (ReactiveFlags.IS_REACTIVE == key) {
      return true;
    }
    console.log(activeEffect, key);
    track(target,key)
    return Reflect.get(target, key, receiver);
  },
/* 属性变动后 要重新执行run 需要把属性和effect关联起来 收集对象上属性关联的effect
 不能光记录属性,容易两个对象有重名的属性,所以需要带着对象记录
 target为对象
 let mapping = {
    target:{
        name:[effect1,effect2,effect3] 一个属性可以在多个页面使用
    }
 }
 */
const targetMap = new WeakMap();

export function createDep(cleanup, key) {
  let dep = new Map() as any;
  dep.cleanup = cleanup;
  dep.name = key;
  return dep;
}

export function track(target, key) {
  // 如果取值操作没有发生在effect中,则不需要收集依赖
  if (!activeEffect) {
    return;
  }
  // 判断是否已经记录过
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }

  let dep = depsMap.get(key);
  if (!dep) {
    // 值为一个不重复数组
    depsMap.set(key, (dep = createDep(() => depsMap.delete(key), key)));
  }
  trackEffect(activeEffect, dep);
}

// 双向记忆
export function trackEffect(effect, dep) {
  // 重新收集依赖 将不需要的移除
  // _trackId:用于记录执行次数,防止一个属性在当前effect中多次收集
  // 进行比较,flag name ==》flag age    把name删掉
  if (dep.get(effect) !== effect._trackId) {
    dep.set(effect, effect._trackId);

    let oldDep = effect.deps[effect._depsLength];
    if (oldDep !== dep) {
      if (oldDep) {
        cleanDepEffect(oldDep, effect);
      }
      effect.deps[effect._depsLength++] = dep;
    } else {
      effect._depsLength++;
    }
  }

2.3 属性更改 触发effect

 set(target, key, value, receiver) {
    // 数据变化后触发对应的effect方法
    const oldValue = target[key];
    let r = Reflect.set(target, key, value, receiver);
    if (oldValue != value) {
      trigger(target, key, value, oldValue);
    }
    return r;
  },
export function trigger(target, key, newValue, oldValue) {
  // 通过对象找到对应的属性 让这个属性对应的effect重新执行
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  const dep = depsMap.get(key); // name 或者 age对应的所有effect
  if (dep) {
    triggerEffects(dep);
  }
}

export function triggerEffects(dep) {
  for (const effect of dep.keys()) {
    if (!effect.scheduler) {
      effect.run();
    } else {
      effect.scheduler();
    }
  }
}

2.4 清除effect依赖

const state = reactive({ flag: true, name: "orange", age: 18 });
      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);

在这里插入图片描述
在这里插入图片描述
在每次收集依赖前先清除,但是此处的清除为初始化_depsLength长度,但是没有真正清除deps中的内容,用于在收集过程中进行比较,实现复用,复用收集结束后,_depsLength为本次真正所依赖的属性长度,如果deps的长度更长,则删除后续的属性依赖。

function preCleanEffect(effect) {
  effect._depsLength = 0;
  effect._trackId++; // 同一个effect执行  id相同
}
function postCleanEffect(effect) {
  if (effect.deps.length > effect._depsLength) {
    for (let i = effect._depsLength; i < effect.deps.length; i++) {
      cleanDepEffect(effect.deps[i], effect); // 删除映射表中对应的effect
    }
    effect.deps.length = effect._depsLength; // 更新列表的长度
  }
}
function cleanDepEffect(dep, effect) {
  dep.delete(effect);
  if (dep.size == 0) {
    dep.cleanup(); // 如果map为空则删除这个属性
  }
}

2.5 effect失活

通过effect返回当前effect实例,然后curEffect.effect.stop()调用stop方法

let runner = effect(() => {
        // 副作用函数 (effect执行渲染了页面)
        console.log("render");
        document.body.innerHTML = state.flag ? state.name : state.age;
      });
      runner.effect.stop()
  stop() {
    cleanupEffect(this);
    this.active = false;
  }

export function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
  const runner = _effect.run.bind(_effect); //调用effect.effect.stop() 停止effect
  runner.effect = _effect;
  return runner;
}

失活后我们可以手动调用 runner()触发effect方法

2.6 调度执行

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
}


 let runner = effect(() => {
        app.innerHTML = state.flag ? state.name : state.age;
      },{
        scheduler:()=>{
          console.log('不执行更新');  // 实现了AOP切片编程
          runner()  
        }
      });

2.7 深度代理

只有当数据被获取的时候才进行代理,如果获取到的数据还是对象,继续代理

get(target, key, receiver) {
    if (ReactiveFlags.IS_REACTIVE == key) {
      return true;
    }
    track(target, key);
    let r = Reflect.get(target, key, receiver);
    if (isObject(r)) {
      return reactive(r);
    }
    return r;
  },

2.8 防止重复代理

判断当前是否正在执行effect,如果effect中会再次修改属性,触发effect,则不做改变


export class ReactiveEffect {
  ...
  public _running = 0; // 是否正在执行
  constructor(public fn, private scheduler) {}
  run() {
    ...
    try {
     ...
      this._running++;
      return this.fn();
    } finally {
      this._running--;
     ...
    }
  }
}


export function triggerEffects(dep) {
  for (const effect of dep.keys()) {
    if (!effect._running) {
      if (!effect.scheduler) {
        effect.run();
      } else {
        effect.scheduler();
      }
    }
  }
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值