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记住当前这个属性
  }
}
相关推荐
qq_54432917几秒前
下载一个项目到跑通的大致过程是什么?
javascript·学习·bug
计算机-秋大田9 分钟前
基于微信小程序的校园失物招领系统设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计
林涧泣19 分钟前
【Uniapp-Vue3】下拉刷新
前端·vue.js·uni-app
浪遏26 分钟前
Langchain.js | Memory | LLM 也有记忆😋😋😋
前端·llm·aigc
luoganttcc1 小时前
华为升腾算子开发(一) helloword
java·前端·华为
九月十九2 小时前
AviatorScript用法
java·服务器·前端
Jane - UTS 数据传输系统2 小时前
VUE+ Element-plus , el-tree 修改默认左侧三角图标,并使没有子级的那一项不展示图标
javascript·vue.js·elementui
_.Switch3 小时前
Python Web开发:使用FastAPI构建视频流媒体平台
开发语言·前端·python·微服务·架构·fastapi·媒体
菜鸟阿康学习编程3 小时前
JavaWeb 学习笔记 XML 和 Json 篇 | 020
xml·java·前端
索然无味io4 小时前
XML外部实体注入--漏洞利用
xml·前端·笔记·学习·web安全·网络安全·php