jiangjiu / blog-md

前端/健身 思考与笔记~

Home Page:https://github.com/jiangjiu/blog-md/issues

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

vue3源码解析 reactive类型

jiangjiu opened this issue · comments

commented

reactive

makeMap

先看个工具函数:

// Make a map and return a function for checking if a key
// is in that map.
//
// IMPORTANT: all calls of this function must be prefixed with /*#__PURE__*/
// So that rollup can tree-shake them if necessary.
export function makeMap(
  str: string,
  expectsLowerCase?: boolean
): (key: string) => boolean {
  const map: Record<string, boolean> = Object.create(null)
  const list: Array<string> = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}

makeMapk可以创建一个map存储字符串中提到的各种类型,来确定某种类型是否支持。

WeakMap

// WeakMaps that store {raw <-> observed} pairs.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
const readonlyToRaw = new WeakMap<any, any>()

正常情况下是需要用两个map去做双向索引的,保存raw和reactive的相互指向关系。

这里使用了WeakMap,相比map可以解决弱引用下的垃圾回收问题。

reactive

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (readonlyToRaw.has(target)) {
    return target
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

// Return a reactive-copy of the original object, where only the root level
// properties are reactive, and does NOT unwrap refs nor recursively convert
// returned properties.
export function shallowReactive<T extends object>(target: T): T {
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    shallowReactiveHandlers,
    mutableCollectionHandlers
  )
}

reactive是composition的核心api,用来创建target对象的Proxy。

reactive和shallowReactive区别在于是否是深度响应式。

通过调用createReactiveObject来创建不同的Proxy对象。

createReactiveObject

function createReactiveObject(
  target: unknown,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target already has corresponding Proxy
  let observed = toProxy.get(target)
  if (observed !== void 0) {
    return observed
  }
  // target is already a Proxy
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  if (!canObserve(target)) {
    return target
  }
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  observed = new Proxy(target, handlers)
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  return observed
}

首先判断传入的target必须是一个对象,否则无法创建Proxy,基本类型使用Ref就好了。

接下来判断target的Proxy是否已经存在,以及是不是已经是个Proxy对象了。

这里使用void 0代替undefined,压缩后更短,避免undefined被重写,但是我很好奇为何不信任压缩工具嘞。

const canObserve = (value: any): boolean => {
  return (
    !value._isVue &&
    !value._isVNode &&
    isObservableType(toRawType(value)) &&
    !rawValues.has(value) &&
    !Object.isFrozen(value)
  )
}

然后判断是否可以observe,只有白名单里的才可以,通常情况下没有被vue记录过,或者非vue实例、非vnode这种内部结点才可以被观测。

针对set map weakmap weakset使用特殊的Proxy handler,其余绝大部分场景使用baseHandler来处理proxy行为。

mutableHandler

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

这几个proxy handler没有太大区别,我们直接看最常用的mutableHandlers。

这个handler重写了这几个常用的属性,一个一个来看。

getter

const get = /*#__PURE__*/ createGetter()


function createGetter(isReadonly = false, shallow = false) {
  return function get(target: object, key: string | symbol, receiver: object) {
    const targetIsArray = isArray(target)
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) && builtInSymbols.has(key)) {
      return res
    }

    if (shallow) {
      !isReadonly && track(target, TrackOpTypes.GET, key)
      return res
    }

    if (isRef(res)) {
      if (targetIsArray) {
        !isReadonly && track(target, TrackOpTypes.GET, key)
        return res
      } else {
        // ref unwrapping, only for Objects, not for Arrays.
        return res.value
      }
    }

    !isReadonly && 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
  }
}

/*#__PURE__*/ 这个标志会告诉压缩工具当前的变量或者函数调用是没有副作用的,没用到的可以直接移除,实现更完善的的tree-shaking。

createGetter中对数组做了特殊处理,忽略了symbol属性的依赖追踪,对Object中的元素自动展开,但是忽略数组中的元素展开。

track和trigger是用来做依赖收集和触发的,后面详细分析。

arrayInstrumentations

vue3 arrayInstrumentations解析
专门写了一下详细分析。

循环依赖

前面的都比较常规,最后这几行看一下 :
当Object对象中的某个属性仍然为object时,需要lazy处理来避免循环依赖。

解决循环依赖有几种方式,引入中间人、延迟处理、缓存等等,对Proxy来说他只能劫持第一层的getter,如果有嵌套对象(如a.b.c)的话,Proxy本身并不能解决问题,如果框架想直接递归遍历的话,性能和边界条件都会出问题。

这里使用lazy处理直接返回一个reactive的object,访问a.b的时候只会触发b这一层的track;访问到a.b.c的时候,框架会依次触发b层track,然后给b这个对象进行reactive处理,这样再访问b.c的时候,因为已经被reactive化,此时的c也进行了track, 就可以避开上面的两个问题。

setter

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    const oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value)
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey = hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

setter相对简单很多,不用多讲。