vue3源码(二)reactive&effect

一.reactive与effect功能

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

js 复制代码
<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基础实现

js 复制代码
// 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 完善重复

js 复制代码
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
js 复制代码
const reactiveMap = new WeakMap(); //内存泄漏
export function reactive(target) {
  if (!isObject(target)) {
    return target;
  }
  const exitProxy = reactiveMap.get(target);
  if (exitProxy) {
    return exitProxy;
  }
  const proxy = new Proxy(target, mutableHandlers);
  reactiveMap.set(target, proxy);
  return proxy;
}
  • 响应式数据标记
    用枚举做标记,响应式对象都有get和set方法,p1初次创建时state没有get和set方法,target[ReactiveFlags.IS_REACTIVE]取值为false,创建p3时,p1响应式,取值为true,直接返回p1
js 复制代码
export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
}

get(target, key, receiver) {
    if (ReactiveFlags.IS_REACTIVE == key) {
      return true;
    }
    return Reflect.get(target, key, receiver);
  },

export function reactive(target) {
  ...,
  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方法,收集依赖

js 复制代码
let activeEffect;
class ReactiveEffect {
  public deps: Array<any> = []; // 判断依赖属性
  public active: boolean = true; // 是否激活
  public parent = undefined; 
  constructor(public fn) {}
  run() {
    if (!this.active) {
      return this.fn();
    }
    try {
      this.parent = activeEffect;
      activeEffect = this;
      return this.fn();
    } finally {
      activeEffect = this.parent;
      this.parent = undefined;
    }
  }
}
export function effect(fn) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

2.2 依赖收集

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

js 复制代码
get(target, key, receiver) {
    if (ReactiveFlags.IS_REACTIVE == key) {
      return true;
    }
    console.log(activeEffect, key);
    track(target,key)
    return Reflect.get(target, key, receiver);
  },
js 复制代码
/* 属性变动后 要重新执行run 需要把属性和effect关联起来 收集对象上属性关联的effect
 不能光记录属性,容易两个对象有重名的属性,所以需要带着对象记录
 target为对象
 let mapping = {
    target:{
        name:[effect1,effect2,effect3] 一个属性可以在多个页面使用
    }
 }
 */
const targetMap = new WeakMap();
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 = new Set()));
  }
  let shouldTrack = !dep.has(key);
  if (shouldTrack) {
    dep.add(activeEffect);
    activeEffect.deps.push(dep); //同时effect记住当前这个属性
  }
}
相关推荐
sg_knight7 分钟前
VSCode如何修改默认扩展路径和用户文件夹目录到D盘
前端·ide·vscode·编辑器·web
一个处女座的程序猿O(∩_∩)O16 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv17 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯23 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552644 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http