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

vue3源码解析 reactive类型 #57

Open
jiangjiu opened this issue Apr 26, 2020 · 0 comments
Open

vue3源码解析 reactive类型 #57

jiangjiu opened this issue Apr 26, 2020 · 0 comments

Comments

@jiangjiu
Copy link
Owner

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相对简单很多,不用多讲。

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

No branches or pull requests

1 participant