Skip to content

关联数据和函数

依赖收集

image-20240529131604509

实现 Effect

这里直接给出 Effect 实现:

js
/**
 * 用于记录当前活动的 effect
 */
export let activeEffect = undefined;
export const targetMap = new WeakMap(); // 用来存储对象和其属性的依赖关系
const effectStack = [];

/**
 * 该函数的作用,是执行传入的函数,并且在执行的过程中,收集依赖
 * @param {*} fn 要执行的函数
 */
export function effect(fn) {
  const environment = () => {
    try {
      activeEffect = environment;
      effectStack.push(environment);
      cleanup(environment);
      return fn();
    } finally {
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
    }
  };
  environment.deps = [];
  environment();
}

export function cleanup(environment) {
  let deps = environment.deps; // 拿到当前环境函数的依赖(是个数组)
  if (deps.length) {
    deps.forEach((dep) => {
      dep.delete(environment);
    });
    deps.length = 0;
  }
}

改造 track

之前 track 仅仅只是简单的打印,那么现在就不能是简单打印了,而是进行具体的依赖收集。

注意依赖收集的时候,需要按照上面的设计一层一层进行查找。

改造 trigger

trigger 要做的事情也很简单,就是从我们所设计的数据结构里面,一层一层去找,找到对应的依赖函数集合,然后全部执行一次。

首先我们需要建立一个设置行为和读取行为之间的映射关系

js
// 定义修改数据和触发数据的映射关系
const triggerTypeMap = {
  [TriggerOpTypes.SET]: [TrackOpTypes.GET],
  [TriggerOpTypes.ADD]: [
    TrackOpTypes.GET,
    TrackOpTypes.ITERATE,
    TrackOpTypes.HAS,
  ],
  [TriggerOpTypes.DELETE]: [
    TrackOpTypes.GET,
    TrackOpTypes.ITERATE,
    TrackOpTypes.HAS,
  ],
};

我们前面在建立映射关系的时候,是根据具体的获取信息的行为来建立的映射关系,那么我们获取信息的行为有:

  • GET
  • HAS
  • ITERATE

这些都是在获取成员信息,而依赖函数就是和这些获取信息的行为进行映射的。

因此在进行设置操作的时候,需要思考一下当前的设置,会涉及到哪些获取成员的行为,然后才能找出该行为所对应的依赖函数。

js
//派发更新

import { SYMBOL_ITERATE_KEY, TrackOpTypes, TriggerOpTypes } from "../utils";

// 定义修改数据和触发数据的映射关系
const triggerTypeMap = {
  [TriggerOpTypes.SET]: [TrackOpTypes.GET],
  [TriggerOpTypes.ADD]: [
    TrackOpTypes.GET,
    TrackOpTypes.ITERATE,
    TrackOpTypes.HAS,
  ],
  [TriggerOpTypes.DELETE]: [
    TrackOpTypes.GET,
    TrackOpTypes.ITERATE,
    TrackOpTypes.HAS,
  ],
};

/**
 * 触发器
 * @param {*} target 原始对象
 * @param {*} type 操作的类型
 * @param {*} key 操作的属性
 */
export default function trigger(target, type, key) {
  // 要做的事情很简单,就是找到依赖,然后执行依赖
  const effectFns = getEffectFns(target, type, key);
  if (!effectFns) return;
  for (const effectFn of effectFns) {
    if (effectFn === activeEffect) continue;
    // 执行依赖函数
    effectFn();
  }
}

/**
 * 通过target找到对应的依赖函数
 * @param {*} target 原始对象
 * @param {*} type 操作的类型
 * @param {*} key 操作的属性
 */
function getEffectFns(target, type, key) {
  const propsMap = targetMap.get(target);
  if (!propsMap) return;

  // 如果是新增或者删除操作,会涉及到额外触发迭代
  const keys = [key];
  if (type === TriggerOpTypes.ADD || type === TriggerOpTypes.DELETE) {
    keys.push(SYMBOL_ITERATE_KEY);
  }

  // 用于存储依赖的函数
  const effectFns = new Set();
  for (const key of keys) {
    const typeMap = propMap.get(key);
    if (!typeMap) continue;

    const trackTypes = triggerTypeMap[type];
    for (const trackType of trackTypes) {
      const dep = typeMap.get(trackType);
      if (!dep) continue;
      for (const effectFn of dep) {
        effectFns.add(effectFn);
      }
    }
  }
  return effectFns;
}

懒执行

有些时候我们想要实现懒执行,也就是不想要传入 effect 的回调函数自动就执行一次,通过配置项来实现

js
//effect函数
/**
 * 该函数的作用,是执行传入的函数,并且在执行的过程中,收集依赖
 * @param {*} fn 要执行的函数
 * @param {*} options 配置项
 */
export function effect(fn, options = {}) {
  const { lazy = false } = options; //+++

  const environment = () => {
    //xxx
  };
  //xxx
  //配置懒执行
  if (!lazy) {
    environment();
  }
  return environment;
}

使用

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

const state = reactive({
  name: "zhangsan",
  age: 18,
  address: "beijing",
  hobby: ["eat", "sleep", "run"],
});

const fn = () => {
  console.log("fn");
  state.age += 1;
};

//使用懒执行就给effect函数传入配置项即可,这样就不会在开始期间就自动执行fn了
const effectFn = effect(fn, {
  lazy: true,
});
effectFn();
state.age = 26;

添加回调

有些时候需要由用户来指定是否派发更新,effect 函数支持用户传入一个回调函数,然后将要依赖的函数作为参数传递回给用户给的回调函数,由用户来决定如何处理。

js
//effect.js
export function effect(fn, options = {}) {
  const { lazy = false } = options;
  const environment = () => {
    //xxx
  };
  environment.deps = [];

  environment.options = options; //+++环境函数䣌配置项,用来记录用户自己处理依赖的函数

  //配置懒执行
  if (!lazy) environment();
  return environment;
}

//trigger.js
js
//trigger.js
export default function trigger(target, type, key) {
  // 要做的事情很简单,就是找到依赖,然后执行依赖
  const effectFns = getEffectFns(target, type, key);
  if (!effectFns) return;
  for (const effectFn of effectFns) {
    if (effectFn === activeEffect) continue;

    // +++ 执行依赖函数
    if (effectFns.options && effectFns.options.scheduler) {
      // 说明用户传递了回调函数,用户期望自己来处理依赖的函数
      scheduler(effectFn);
    } else {
      effectFn();
    }
  }
}

使用

js
const effectFn = effect(fn, {
  lazy: true,
  //直接配置scheduler函数,参数是响应式属性记录的依赖函数,交给用户自己处理
  scheduler(effectFn) {
    const a = 0;
    if (a) {
      effectFn();
    }
  },
});
effectFn();

-EOF-

MIT License