Gyron.js 的响应式原理

158 阅读2分钟

Gyron.js 是一款前端响应式框架,官方文档在gyron.cc/

它使用 Proxy 接口作为响应式底层代理,只要访问了对象的某个属性或者删除了某个属性都都可以被监听到,然后可以通过一个内部状态记录所有依赖关系,当一个被依赖的数据发生变更时,就通过内部状态找到所有依赖当前对象的数据,然后再更新它们。

在 Gyron.js 中,声明一个可响应的数据只能通过 useValue、useComputed、useMemo、useReactive 这几种方法。当你声明一个响应式数据后,在组件内使用 value 访问值之后就会自动建立依赖关系,当值发生变更时,就会自动触发依赖的组件开始更新。如果访问响应式数据的逻辑不在组件的第一次执行中,换句话说就是在回调或者异步函数中访问响应式函数不会自动建立依赖关系,不过你可以在组件内第一层 scope 中访问这个响应式变量,这样它就会自动建立依赖。

let activeEffect = null;
const effectTracks = new WeakMap();

const observed = new Proxy(
  {
    count: 0,
  },
  {
    get(target, property, receiver) {
      if (activeEffect) {
        // TODO trace
      }
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      if (effectTracks.has(target)) {
        // TODO trigger
      }
      return Reflect.set(target, property, value, receiver);
    },
  }
);

function createEffect(fn) {
  const effect = {
    run: () => {
      // 在执行 run 方法时改变 activeEffect 指向,让依赖的值知道被谁依赖。
      activeEffect = effect;
      return fn();
    },
  };
  return effect;
}

const effect = createEffect(() => {
  console.log(observed.count);
});
effect.run();

上述代码描述了一个很简单的响应式模型,利用了两个模块变量保存数据的依赖关系,当数据发生变更时执行更新。(上方代码还有很多问题,比如未及时清理 activeEffect 变量的指向导致依赖关系混乱。)

其中有两处注释 TODO 没有实现具体的逻辑,读者可以自由发挥,实现并完成下方的测试用例。

const effect = createEffect(() => {
  console.log(observed.count);
});
effect.run();
// console.log 0
observed.count = 1;
// console.log 1
observed.count = 10;
// console.log 10

如果想不到答案也没有关系,我会在后面的文章中告诉大家如何实现。