【框架实现】初级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>
相关推荐
余生H几秒前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼1 分钟前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js
程序员-珍3 分钟前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai8 分钟前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默20 分钟前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_8572979130 分钟前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
茶卡盐佑星_40 分钟前
meta标签作用/SEO优化
前端·javascript·html
Ink1 小时前
从底层看 path.resolve 实现
前端·node.js
金灰1 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
茶卡盐佑星_1 小时前
说说你对es6中promise的理解?
前端·ecmascript·es6