抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

vue3的设计真是太妙了,尤雨溪我男神!

逻辑分析

vue3文档上说:

  • reactive:返回对象的响应式副本

  • watchEffect:为了根据响应式状态自动应用和重新应用副作用,我们可以使用 watchEffect 方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

我的理解:

  • reactive会将传入的对象包装成Proxy对象

  • watchEffect中使用到的被reactive包裹的属性更新时,会被trigger触发调用watchEffect

以此通过这种方式实现了数据间的响应式更新。

这张图画的不错,忘了是从哪下载下来的了,如果作者看到请务必联系我(bushi)

代码实现

我的目录结构:

│  index.html
│  index.ts
└─reactive
        effect.ts
        mutableHandlers.ts
        reactive.ts

index.html

<div id="root"></div>

index.ts


import { effect } from "./reactive/effect";
import { reactive } from "./reactive/reactive";

const label = document.querySelector("#root");
const state = reactive({
  name: "孙泽辉",
  age: 20,
});

effect(() => {
  label.innerHTML = `姓名:${state.name},年龄:${state.age}`;
});

setTimeout(() => {
  state.name = "孙爷爷";
}, 2000);

setTimeout(() => {
  state.age = 21;
}, 4000);

首先要有reactive进行包裹的state,再写一个Effect写好逻辑,Effect第一次会执行一次,之后只有当Effect中用到的state更新时才会执行。

reactive/reactive.ts


import { baseHandler } from "./mutableHandlers";

export function reactive(target: any) {
  return createReactiveObject(target);
}
const reactiveMap = new WeakMap<Object, Object>();
function createReactiveObject(target: any) {
  const existProxy = reactiveMap.get(target);

  return existProxy ? existProxy : new Proxy(target, baseHandler);
}

reactive/effect.ts


export function effect(f: Function) {
  const effect = createReactiveEffect(f);

  effect();
}

let activeEffect: Function = () => {};
const activeEffectStacked: Function[] = [];
function createReactiveEffect(f: Function) {
  const effect = () => {
    if (!activeEffectStacked.includes(effect)) {
      activeEffectStacked.push(effect);
      activeEffect = effect;
      try {
        f();
      } finally {
        activeEffectStacked.pop();
        activeEffect = activeEffectStacked[activeEffectStacked.length - 1];
      }
    }
  };
  return effect;
}

// 保存函数中变量的信息
const targetMap = new WeakMap<Object, Map<string, Set<Function>>>();
export function track(target: any, key: string) {
  let depMap = targetMap.get(target);
  if (!depMap) {
    targetMap.set(target, (depMap = new Map()));
  }

  let depSet = depMap.get(key);
  if (!depSet) {
    depMap.set(key, (depSet = new Set()));
  }
  depSet.add(activeEffect);
  console.log(`track:${key}`, targetMap);
}

export function trigger(target: any, key: string) {
  let depMap = targetMap.get(target);
  if (!depMap) {
    return;
  }

  let depSet = depMap.get(key);
  if (!depSet) {
    return;
  }
  console.log(`trigger:${key}`, targetMap);

  depSet.forEach((effect) => {
    effect && effect();
  });
}

第一次执行Effect,经过层层套娃,将Effect临时存到一个EffectStatck中,然后让函数跑起来,如果Effect中有用到state,那一定会触发Proxy的getter函数,在getter里会调用track,track将当前正在跑的Effect存到targetMap这个数据结构中,如上图。

reactive\mutableHandlers.ts


import { track, trigger } from "./effect";

export const baseHandler = {
  get(target: any, key: string, receiver: any): any {
    console.log("getter:", key);
    const value = Reflect.get(target, key, receiver);
    track(target, key);
    return value;
  },
  set(target: any, key: string, newValue: any, receiver: any): any {
    console.log("setter:", key, newValue);
    const result = Reflect.set(target, key, newValue, receiver);
    trigger(target, key);
    return result;
  },
};

当state中的属性有修改时,会触发Proxy的setter函数,在setter里会调用trigger(在reactive/effect.ts中),trigger将变更的属性的Effect从targetMap中全部拿出来,全跑一遍。

没写注释,有空写上。

看看效果吧!

效果

img

实现效果:state中的数据更新时,会调用Effect更新视图

缺点和不足:

  • 没有实现嵌套对象的reactive,应该也就一行代码的事
  • 没写注释
  • onTrigger、onTrack钩子没写

评论