通过源码深入了解 vue3 的 ref vs reactive

2023-03-30  本文已影响0人  VioletJack

最近刚开始用 vue3,其中组合式 API 的 ref 和 reactive 两者让我有些困惑:

于是就有了看源码的想法,源码是直接从 https://unpkg.com/vue@3/dist/vue.global.js 上保存下来的。后续就可以在源码上面调试学习啦。

结论

先说结论!(我知道很多朋友不喜欢看过程,只要结论。比如我自己 0,0)

他们最终的目的都是能响应式渲染模板(即数据变化后网页内容也随之变化)。

ref

源码

先看下 ref 的源码,ref() 函数执行了 createRef() 函数,而 createRef() 中实例化了 RefImpl 类。

function ref(value) {
  return createRef(value, false)
}

function createRef(rawValue, shallow) {
  // 如果已经是 ref 则直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

RefImpl 类中除了构造函数,只有一个 value 内部值的 setter 和 getter 函数。在构造函数中 _rawValue 是原始数据,而 _value 是响应数据(如果数据是对象类型则为 Proxy)。

那么 _value 是如何来的?如果不是浅层响应式,则会调用 toReactive 函数。

class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow
    this.dep = undefined
    this.__v_isRef = true
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }
  get value() {
    trackRefValue(this)
    return this._value
  }
  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

toRaw() 函数中,递归获取数据的原始数据。(reactive() 函数返回的代理对象中带有 __v_raw 标签,它会让 getter 函数返回原始数据)

function toRaw(observed) {
  const raw = observed && observed['__v_raw' /* ReactiveFlags.RAW */]
  return raw ? toRaw(raw) : observed
}

toReactive() 函数中,就可以看到已经使用 reactive() 函数的逻辑了。

如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。

const toReactive = (value) => (isObject(value) ? reactive(value) : value)

顺便瞅一眼 isObject() 函数,对象类型的判定就是 typeof val === 'object'。不过由于 JavaScript 的缺陷,所以 typeof null 也是 object,需要排除掉。

const isObject = (val) => val !== null && typeof val === 'object'

小实验

实验出真知

const a = ref('123')
a.value += '456'
// '123456'
const b = ref(6)
b.value += 8
// 14
const c = ref(false)
c.value = !c.value
// true

const r3 = ref(false)
r3.value = true
r3.value = 'oh li gei' // value 是不限定类型的
// oh li gei

const d = ref(null)
// null
const e = ref(undefined)
// undefined
const f = ref(Symbol())
// Symbol()

// 这里打赢 ref 返回的对象
const g = ref({ a: [1, 2, 3] })
// RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: Array(4), _value: Proxy}
const h = ref([3, 4, 5, 6])
// RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: Array(4), _value: Proxy}
const i = ref(new Set())
// RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: Set(1), _value: Proxy}

// 验证 toRaw 函数的 __v_raw 属性
const x = reactive({ a: 1, b: { c: { d: 3 } } })
const y = ref(x)
console.log('x', x.__v_raw) // { a: 1, b: { c: { d: 3 } } }
console.log('y', y.__v_raw) // undefined

reactive

源码

reactive()

reactive() 函数除了判断只读外就只是调用了 createReactiveObject() 函数。

createReactiveObject() 函数中,排除了各种不需要代理的情况,并根据数据类型不同进行不同的代理逻辑处理。最后将代理结构记录到一个 Map 中。

function reactive(target) {
  // 如果 target 是 Readonly 的代理,返回自身
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}

function createReactiveObject(
  target,
  isReadonly,
  baseHandlers,
  collectionHandlers,
  proxyMap,
) {
  // target 不是对象类型,返回自身
  if (!isObject(target)) {
    {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target 已经是代理,返回自身
  if (
    target['__v_raw' /* ReactiveFlags.RAW */] &&
    !(isReadonly && target['__v_isReactive' /* ReactiveFlags.IS_REACTIVE */])
  ) {
    return target
  }
  // target 已经有响应的代理,返回代理
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  /**
   * 判断 target 数据类型
   * 0 无效,直接返回
   * 1 COMMON 类型,使用 baseHandlers 代理配置
   * 2 COLLECTION 类型,使用 collectionHandlers 代理配置
   */
  const targetType = getTargetType(target)
  if (targetType === 0 /* TargetType.INVALID */) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === 2 /* TargetType.COLLECTION */
      ? collectionHandlers
      : baseHandlers,
  )
  // 记录代理关系
  proxyMap.set(target, proxy)
  return proxy
}

数据类型判断

判断数据类型的代码如下,根据不同的数据分为:

类型的获取是通过 Object.prototype.toString.call(target) 获取到 '[object Set]' 这类字符串,并截取 Set 这段有效字符串返回。

function getTargetType(value) {
  return value['__v_skip' /* ReactiveFlags.SKIP */] ||
    !Object.isExtensible(value)
    ? 0 /* TargetType.INVALID */
    : targetTypeMap(toRawType(value))
}

function targetTypeMap(rawType) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return 1 /* TargetType.COMMON */
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return 2 /* TargetType.COLLECTION */
    default:
      return 0 /* TargetType.INVALID */
  }
}

普通类数据代理

reactive() 函数中可以看到,代理配置分别使用的是 mutableHandlers 和 mutableCollectionHandlers。

普通类型的代理配置 mutableHandlers 如下。这里代码量较大,暂时就只贴出 getter 和 settter 函数。

const mutableHandlers = {
  get: get$1, // get 方法用于拦截某个属性的读取操作
  set: set$1, // set 方法用来拦截某个属性的赋值操作
  deleteProperty, // deleteProperty 方法用于拦截 delete 操作
  has: has$1, // has() 方法用来拦截HasProperty操作
  ownKeys, //  ownKeys() 方法用来拦截对象自身属性的读取操作
}

在 createGetter 中的处理逻辑如下:

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

function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key, receiver) {
    if (key === '__v_isReactive' /* ReactiveFlags.IS_REACTIVE */) {
      return !isReadonly
    } else if (key === '__v_isReadonly' /* ReactiveFlags.IS_READONLY */) {
      return isReadonly
    } else if (key === '__v_isShallow' /* ReactiveFlags.IS_SHALLOW */) {
      return shallow
    } else if (
      key === '__v_raw' /* ReactiveFlags.RAW */ &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }
    const targetIsArray = isArray(target)
    if (!isReadonly) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }
    const res = Reflect.get(target, key, receiver) // Reflect.get 方法查找并返回target对象的name属性
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    if (!isReadonly) {
      track(target, 'get' /* TrackOpTypes.GET */, key)
    }
    if (shallow) {
      return res
    }
    if (isRef(res)) {
      // ref 解构取值
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
    if (isObject(res)) {
      // 递归生成响应式代理
      return isReadonly ? readonly(res) : reactive(res)
    }
    return res
  }
}

在 set 函数中

const set$1 = /*#__PURE__*/ createSetter()

function createSetter(shallow = false) {
  return function set(target, key, value, receiver) {
    let oldValue = target[key]
    // 不需要更新的情况
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    if (!shallow) {
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue) // 递归转为原始数据
        value = toRaw(value) // 递归转为原始数据
      }
      // ref 解构赋值
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    }
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver) // Reflect.set 方法设置 target 对象的 name 属性等于value。
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, 'add' /* TriggerOpTypes.ADD */, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, 'set' /* TriggerOpTypes.SET */, key, value, oldValue)
      }
    }
    return result
  }
}

收集器类数据代理

收集器类型的代理配置只有一个 getter 函数,它对收集器类型数据的 API 进行了定义。

如果调用 set.add() map.get() 这类 API,就会去调用 instrumentations 对象中相应的函数。否则就返回代理目标 target 自身。

const mutableCollectionHandlers = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, false),
}

function createInstrumentationGetter(isReadonly, shallow) {
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  return (target, key, receiver) => {
    if (key === '__v_isReactive' /* ReactiveFlags.IS_REACTIVE */) {
      return !isReadonly
    } else if (key === '__v_isReadonly' /* ReactiveFlags.IS_READONLY */) {
      return isReadonly
    } else if (key === '__v_raw' /* ReactiveFlags.RAW */) {
      return target
    }
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver,
    )
  }
}

const mutableInstrumentations = {
  get(key) {
    return get(this, key)
  },
  get size() {
    return size(this)
  },
  has,
  add,
  set,
  delete: deleteEntry,
  clear,
  forEach: createForEach(false, false),
}

function add(value) {
  value = toRaw(value) // 去代理
  const target = toRaw(this) // 去代理
  const proto = getProto(target)
  const hadKey = proto.has.call(target, value)
  if (!hadKey) {
    target.add(value) // 执行原生函数
    trigger(target, 'add' /* TriggerOpTypes.ADD */, value, value)
  }
  return this
}

function set(key, value) {
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)
  let hadKey = has.call(target, key)
  if (!hadKey) {
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else {
    checkIdentityKeys(target, has, key)
  }
  const oldValue = get.call(target, key)
  target.set(key, value)
  if (!hadKey) {
    trigger(target, 'add' /* TriggerOpTypes.ADD */, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, 'set' /* TriggerOpTypes.SET */, key, value, oldValue)
  }
  return this
}

源码小结

小实验

以下写法 reactive() 返回值是其自身,但不是响应式的。而且 vue 会发出警告:value cannot be made reactive: 123

var a = reactive('123')
// 123

function add() {
  a += '456' // 变量 a 有变化,但是 HTML 无变化
}

var b = reactive(6)
b += 8
// 16

const c = reactive(false)
// false
setTimeout(() => {
  c = true // c 变为 true,但是 HTML 无变化
}, 1000)

const d = reactive(null)
// null

const e = reactive(undefined)
// undefined

const f = reactive(Symbol())
// Synbol()

下面这些情况可以正常使用 reactive() 函数。

const g = reactive({ a: 1, b: { c: 3 } })
g.a++
// Proxy: { a: 2, b: { c: 3 } }
setInterval(() => {
  // 网页会每秒变化数据
  g.a++
  g.b.c += 2
}, 1000)

const h = reactive([3, 4, 5, 6])
h.push(8)
// Proxy: {0: 3, 1: 4, 2: 5, 3: 6, 4: 8}

const i = reactive(new Set())
i.add('2')
i.add({ b: 3 })
i.add(321)
// Proxy: { "Set(4)": [ "2", { "b": 3 }, 321 ] }
setTimeout(() => {
  i.add(null)
  // Proxy: { "Set(4)": [ "2", { "b": 3 }, 321, null ] }
}, 1000)

const j = reactive(new Map())
j.set('yo', 'good')
j.set('x', { b: 3 })
setTimeout(() => {
  j.delete('x')
  // Proxy: { "Map(1)": { "yo =>": "good" } }
}, 1000)

既然 reactive 函数可以解构 ref,那么进行一些尝试。以下是官网的原话。

值得注意的是,当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。

但在实际实验下来发现这句话并不严谨。

var a = ref(new Map())
var b = reactive(a)
a.value.set('a', 1)
b.value.set('b', 2) // 需要加上 value
// { "Map(2)": { "a =>": 1, "b =>": 2 } }

console.log('a === b', a.value === b.value) // true

var a = ref(new Set())
var b = reactive({ a })
a.value.add(1)
b.a.add(2) // ! 被对象包裹的 Map 是可以被解构的
// { "Map(2)": { "a =>": 1, "b =>": 2 } }
console.log('a === b', a.value === b.a) // true

尝试了 Object、Array、Set 后发现,被 ref 函数返回的对象如果直接传给 reactive 函数是不会被解构的,但如果 ref 对象被对象符号包裹 reactive({ ref: ref(new Set()) }) 的情况下是可以被解构的。

最后

本文我们先提出了 ref 和 reactive 的疑问,然后给出结果。再从源码层面逐步分析了 ref 和 reactive 函数。也算是基本掌握其原理了。

关于 ref 和 reactive 的内容就这么多啦,希望对你有用。

本文正在参加「金石计划」

上一篇 下一篇

猜你喜欢

热点阅读