【框架实现】初级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>
相关推荐
黄智勇3 分钟前
xlsx-handlebars 一个用于处理 XLSX 文件 Handlebars 模板的 Rust 库,支持多平台使
前端
brzhang1 小时前
为什么 OpenAI 不让 LLM 生成 UI?深度解析 OpenAI Apps SDK 背后的新一代交互范式
前端·后端·架构
brzhang2 小时前
OpenAI Apps SDK ,一个好的 App,不是让用户知道它该怎么用,而是让用户自然地知道自己在做什么。
前端·后端·架构
程序员王天2 小时前
【开发AGIC】Vue3+NestJS+DeepSeek AI作业批改系统(已开源)
vue.js·ai编程·nestjs
井柏然2 小时前
前端工程化—实战npm包深入理解 external 及实例唯一性
前端·javascript·前端工程化
昔冰_G3 小时前
Vue内置组件KeepAlive——缓存组件实例
vue.js·缓存·vue3·vue2·keep-alive·vue组件缓存·vue内置组件
IT_陈寒3 小时前
Redis 高性能缓存设计:7个核心优化策略让你的QPS提升300%
前端·人工智能·后端
aklry3 小时前
elpis之动态组件机制
javascript·vue.js·架构
井柏然3 小时前
从 npm 包实战深入理解 external 及实例唯一性
前端·javascript·前端工程化
羊锦磊4 小时前
[ vue 前端框架 ] 基本用法和vue.cli脚手架搭建
前端·vue.js·前端框架