Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue 3.0 reactivity 源码详解 #5

Open
buppt opened this issue Jun 14, 2020 · 0 comments
Open

vue 3.0 reactivity 源码详解 #5

buppt opened this issue Jun 14, 2020 · 0 comments

Comments

@buppt
Copy link
Owner

buppt commented Jun 14, 2020

前置知识

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant