【框架实现】初级vue3响应性的实现

构建reactive函数,获取proxy实例

整个reactive函数,本质是返回了一个proxy实例,我们先去实现这个reactive函数,得到proxy实例;

1.创建packages/reactivity/src/reactive.ts模块:

reactive.ts 复制代码
import { mutableHandlers } from "./baseHandlers";

export const reactiveMap = new WeakMap<object, any>();

export function reactive(target: object) {
  return createReactiveObject(target, mutableHandlers, reactiveMap);
}
function createReactiveObject(
  target: object,
  baseHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<object, any>
) {
  const existingProxy = proxyMap.get(target);
  // 已经被代理了直接返回
  if (existingProxy) {
    return existingProxy;
  }
  // 没有被代理则生成proxy实例
  const proxy = new Proxy(target, baseHandlers);
  // 缓存proxy
  proxyMap.set(target, proxy);
  return proxy;
}

2.创建packages/reactivity/src/baseHandlers.ts模块;

baseHandlers.ts 复制代码
export const mutableHandlers: ProxyHandler<object> = {};

3.创建packages/reactivity/src/index.ts;

index.ts 复制代码
export { reactive } from "./reactive";

5.创建packages/vue/src/index.ts;

index.ts 复制代码
export { reactive } from "@vue/reactivity";

在终端执行npm run build;生成dist目录下的文件,此时我们的reactive函数就被成功导出了;

接下来我们去创建对应的测试实例;创建packages/vue/examples/reactivity/reactive.html;

reactive.html 复制代码
// 导入刚才打的包
<script src="../../dist/vue.js"></script>

<script>
    const { reactive } = Vue;

    const obj = reactive({
      name: "张三",
    });
</script>

运行之后打印:

实现createGetter和createSetter方法

在packages/reactivity/src/baseHandlers.ts中修改:

baseHandlers.ts 复制代码
import { track, trigger } from "./effect";
const get = createGetter();
const set = createSetter();

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
};

function createGetter() {
  return function get(target: object, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver);

    // 依赖收集
    track(target, key);
    return res;
  };
}

function createSetter() {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ) {
    const result = Reflect.set(target, key, value, receiver);
    // 触发依赖
    trigger(target, key, value);
    return result;
  };
}

新建packages/reactivity/src/effect.ts

effect.ts 复制代码
/**
 * 收集依赖
 * @param target
 * @param key
 */
export function track(target: object, key: unknown) {
  console.log("收集依赖");
}

/**
 * 触发依赖
 * @param target
 * @param key
 * @param newValue
 */
export function trigger(target: object, key: unknown, newValue: unknown) {
  console.log("触发依赖");
}

接下来测试一下,是否会触发track和trigger方法;记得要重新build;

js 复制代码
<script>
    const { reactive } = Vue;

    const obj = reactive({
      name: "张三",
    });

    console.log("obj--->", obj.name);
    obj.name = "李四";
</script>

track和trigger方法被成功打印出来了;

小插曲:给项目加上热更新

给package.json新加一条指令;

package.json 复制代码
"scripts": {
  "dev": "rollup -c -w",
}

构建effect函数,生成ReactiveEffect实例

创建好reactive实例之后,接下来我们需要触发effect;

js 复制代码
effect(() => {
  document.querySelector("#app").innerText = obj.name;
});

在effect中,生成了ReactiveEffect实例,并且触发了getter;接下来实现effect方法;

1.在packages/reactivity/src/effect.ts中,创建effect方法;

effect.ts 复制代码
export function effect<T = any>(fn: () => T) {
  const _effect = new ReactiveEffect(fn);
  _effect.run();
}

export let activeEffect: ReactiveEffect | undefined;

export class ReactiveEffect<T = any> {
  constructor(public fn: () => T) {}
  run() {
    activeEffect = this;
    return this.fn();
  }
}

2.导出effect方法;

去修改我们的测试实例;

js 复制代码
<script>
    const { reactive, effect } = Vue;

    const obj = reactive({
      name: "张三",
    });

    effect(() => {
      document.querySelector("#app").innerText = obj.name;
    });
</script> 

此时页面中展示出来了张三

实现track和trigger方法

依赖收集和依赖触发原理

  • track用来依赖收集;
  • trigger用来依赖触发;

响应性是:当响应性数据触发setter时执行fn函数;如果想要实现这个目的,那么必须在getter时去收集当前的fn函数,以便在setter时可以执行对应的fn函数;

reactive.ts 复制代码
export const reactiveMap = new WeakMap<object, any>();

WeakMap的key必须是一个对象,并且key是一个弱引用的;举个例子

js 复制代码
const obj = reactive({
      name: "张三",
    });

effect(() => {
  document.querySelector("#app").innerText = obj.name;
});

1.WeakMap:
  1.key:响应性对象 (此时为obj)
  2.value:Map对象 
    1.key:响应性对象的指定属性 (name)
    2.value:指定对象的指定属性的执行函数 (fn函数)

实现track依赖收集函数

依赖收集的本质就是实现activeEffect和targetMap的绑定:

effect.ts 复制代码
type KeyToDepMap = Map<any, ReactiveEffect>;
const targetMap = new WeakMap<any, KeyToDepMap>();

/**
 * 收集依赖
 * @param target
 * @param key
 */
export function track(target: object, key: unknown) {
  // activeEffect是fn函数
  if (!activeEffect) return;
  console.log("收集依赖");
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  depsMap.set(key, activeEffect);
  console.log("targetMap--->", activeEffect, targetMap);
}

此时targetMap的值为:

实现trigger触发依赖函数

所有的依赖关系都保存到了targetMap之中,所以依赖触发也就是从targetMap中读取对应的effect,然后执行对应的fn函数;

effect.ts 复制代码
/**
 * 触发依赖
 * @param target
 * @param key
 * @param newValue
 */
export function trigger(target: object, key: unknown, newValue: unknown) {
  console.log("触发依赖");
  const depsMap = targetMap.get(target);

  if (!depsMap) {
    return;
  }
  const effects = depsMap.get(key) as ReactiveEffect;

  if (!effects) {
    return;
  }
  effects.fn();
}

修改一个测试实例;

js 复制代码
<script>
    const { reactive, effect } = Vue;

    const obj = reactive({
      name: "张三",
    });

    effect(() => {
      document.querySelector("#app").innerText = obj.name;
    });

    setTimeout(() => {
      obj.name = "李四";
    }, 4000);
</script>
相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试