Skip to content

vue 3.0 reactivity 源码详解 #5

@buppt

Description

@buppt

前置知识

Set、Map、WeakMap、Reflect、Proxy

使用

reactive 用来将需要观察的对象转换成可以观察的对象。
effect 定义了一个回调函数,当其中某个可观察对象发生变化时,触发回调。

import { reactive } from './reactive'
import { effect } from './effect'

const state = reactive({
    count: 0,
    age: 18
})

effect(() => {
    console.log('effect: ' + state.count)
})

实现

Reactive

export function reactive(target) {
  const observed = new Proxy(target, handler)
  return observed
}

当要观察的数据结构是 Set, Map, WeakSet, WeakMap 时,用 collectionHandlers
others : baseHandlers

collectionHandlers 又分为 mutableCollectionHandlers、readonlyCollectionHandlers
baseHandlers 分 mutableHandlers、readonlyHandlers

export const mutableHandlers = {
  get: createGetter(false),
  set,
  deleteProperty,
  has,
  ownKeys
};

function createGetter(isReadonly: boolean, shallow = false) {
  return function get(target: object, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver);
    // 把 effect 的回调保存起来
    track(target, TrackOpTypes.GET, key);

    return isObject(res)
      ? isReadonly
        ? // need to lazy access readonly and reactive here to avoid
          // circular dependency
          readonly(res)
        : reactive(res)
      : res;
  };
}

function set(target, key, value, receiver) {
  const hadKey = hasOwn(target, key);
  const oldValue = target[key];
  const result = Reflect.set(target, key, value, receiver);
  if (!hadKey) {
    trigger(target, "add", key);
  } else if (value !== oldValue) {
    trigger(target, "set", key);
  }
  return result;
}

function deleteProperty(target, key) {
  const hadKey = hasOwn(target, key);
  const oldValue = target[key];
  const result = Reflect.deleteProperty(target, key);
  if (hadKey) {
    trigger(target, "delete", key);
  }
  return result;
}
// 拦截判断对象是否具有某个属性时的操作,返回一个布尔值
function has(target: object, key: string | symbol): boolean {
  const result = Reflect.has(target, key);
  track(target, TrackOpTypes.HAS, key);
  return result;
}
// 拦截对象自身属性的读取操作 Object.keys() for...in
function ownKeys(target: object): (string | number | symbol)[] {
  track(target, TrackOpTypes.ITERATE, ITERATE_KEY);
  return Reflect.ownKeys(target);
}

effect

现在还差 effect 的实现、get 中 track 的实现,set 中 trigger 的实现
存储数据的结构如下图所示。
1

const targetMap = new WeakMap();
const effectStack = []; // 记录 effect

export function effect(fn) {
  const effect = run(effect, fn, null);
  effect();
  return effect;
}

function run(effect, fn, args) {
  if (effectStack.indexOf(effect) === -1) {
    try {
      effectStack.push(effect);
      return fn(...args);
    } finally {
      effectStack.pop();
    }
  }
}

export function track(target, operationType, key) {
  const effect = effectStack[effectStack.length - 1];
  if (effect) {
    let depsMap = targetMap.get(target);
    if (depsMap === void 0) {
      targetMap.set(target, (depsMap = new Map()));
    }

    let dep = depsMap.get(key);
    if (dep === void 0) {
      depsMap.set(key, (dep = new Set()));
    }

    if (!dep.has(effect)) {
      dep.add(effect);
    }
  }
}

track 就是将 effect 回调函数添加到其中使用的被观察对象的 set 集合中。当触发 trriger 函数时取出来触发回调。

export function trigger(target, operationType, key) {
  const depsMap = targetMap.get(target);
  if (depsMap === void 0) {
    return;
  }
  const effects = new Set();
  if (key !== void 0) {
    // 将依赖这个key的所有监听函数推到相应队列中
    const dep = depsMap.get(key);
    dep &&
      dep.forEach(effect => {
        effects.add(effect);
      });
  }
  if (operationType === "add" || operationType === "set") {
    // 如果原始数据是数组,则key为length,否则为Symbol,这里最后会讲
    const iterationKey = Array.isArray(target) ? "length" : Symbol("iterate");
    const dep = depsMap.get(iterationKey);
    dep &&
      dep.forEach(effect => {
        effects.add(effect);
      });
  }
  effects.forEach(effect => {
    effect();
  });
}

当观察对象是 Set, Map, WeakSet, WeakMap 时使用 collectionHandlers
因为集合没有 set 方法,所以新创建了一个和集合对象具有相同属性和方法的普通对象,在集合对象 get 操作时将 target 对象换成新创建的普通对象。

const mutableInstrumentations: Record<string, Function> = {
  get(this: MapTypes, key: unknown) {
    return get(this, key, toReactive)
  },
  get size(this: IterableCollections) {
    return size(this)
  },
  has,
  add,
  set,
  delete: deleteEntry,
  clear,
  forEach: createForEach(false)
}

为什么可以监听数组

其实是 Proxy 的能力,可以监听数组的 index 和 length

const arr = [0];
const obj = new Proxy(arr, {
  get: function(target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function(target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});
obj.push(0);

2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions