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时 不会触发name的get

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);
  • 响应式数据缓存,防止重复代理
    使用map,key为对象,值为响应式对象,实现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函数

vue2与vue3早期的依赖收集采用的都是栈方式存储,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记住当前这个属性
  }
}
相关推荐
lpfasd1232 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass
Data analyse4565 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
xiaogai_gai5 小时前
金蝶收料通知单查询策略实战教程:从源系统拉取到中间层落地的完整路径
java·前端·数据库
环境栈笔记6 小时前
指纹浏览器 WebRTC 怎么设置?从 ICE 候选、代理出口到功能验证
前端·人工智能·后端·自动化
赵大仁6 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
xiancai_xianyu6 小时前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
小羊没烦恼!7 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
史瓦辛格7 小时前
给 DeepSeek Harness 写费用插件:把「这次会话花了多少钱」算准
前端·deepseek
星光不问赶路人7 小时前
Vue 3 + TypeScript 中 enum 的使用实践
前端·vue.js
鸥梨菌Honevid7 小时前
UVM 中 REQ 和 RSP 类型详解:从通信模型到 item_done 与 put_response
java·前端·servlet