vue3源码之旅-ref

2,147 阅读2分钟

更多文章

前言

上篇介绍了reactive,这篇介绍一下ref相关的api

简化代码

vue3-ref源码位置

class

上篇说reactiveProxy息息相关,而refreactiveclass息息相关,接下来看源码

ref

源码实现:

export function ref(value?: unknown) {
  return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

vue通过createRef返回RefImpl,RefImpl是一个class,看一下vue是如何实现RefImpl

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    this._rawValue = _shallow ? value : toRaw(value)
    this._value = _shallow ? value : convert(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

通过代码可以看到get获取值时是返回this._value,默认情况下通过convert获取真正的数据,那么我们一起看一下convert是如何实现的

const convert = <T extends unknown>(val: T): T => isObject(val) ? reactive(val) : val

此处可以看出一些端倪了,当ref入参为对象时会走reactiveproxy的代理,否则走RefImplsetget方法,这也就解释了开篇说的refreactiveclass息息相关,看一下简化后的代码

// 若为对象走reactive
const convert = val => isObject(val) ? reactive(val) : val

class RefImpl {
  constructor(value, shallow) {
    this.__v_isRef = true
    this._rawValue = shallow ? value : toRaw(value)
    this._value = shallow ? value : convert(value)
    this._shallow = shallow
  }

  get value() {
    return this._value
  }

  set value(newValue) {
    newValue = this._shallow ? newValue : toRaw(newValue)
    if(hasChanged(newValue, this._rawValue)) {
      this._rawValue = newValue;
      this._value = this._shallow ? newValue : convert(newValue)
    }
  }
}

function createRef(value, shallow) {
  if(isRef(value)) return value
  return new RefImpl(value, shallow);
}

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

isRef

RefImpl中有一个只读的__v_isRef属性,这也是判断是否为ref的依据

function isRef(r) {
  return r && r.__v_isRef === true
}

unref

官网解释:如果参数是一个 ref,则返回内部值,否则返回参数本身

function unref(ref) {
  return isRef(ref) ? ref.value : ref
}

toRef

toRef的实现同样是通过自定义classsetget方法对value属性进行操作,从而达到操控源对象

class ObjectRefImpl {
  constructor(_object, _key) {
    this.__v_isRef = true
    this._object = _object
    this._key = _key
  }
  get value() {
    // 获取reactive值
    return this._object[this._key]
  }
  set value(newVal) {
    // 设置reactive值
    this._object[this._key] = newVal
  }
}
function toRef(target, key) {
  const value = target[key]
  return isRef(value) ? value : new ObjectRefImpl(target, key)
}

toRefs

toRefs是借助toRef对响应式对象的操作

function toRefs(object) {
  if (!isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

shallowRef

shallowRef会跟着ref的改变,但不会让.value变为响应式的,入参时将shallow设置为true,此时ref的入参为对象时将不会在走convert

function shallowRef(value) {
  return createRef(value, true)
}

customRef

customRef接收一个工厂函数,工厂函数需返回自定义的getset方法,当获取和赋值的时候会分别调用自定义的getset方法,此处并未涉及数据收集过程,仅是单纯的逻辑实现

class CustomRefImpl {
  constructor(factory) {
    const { get, set } = factory()
    this._get = get
    this._set = set
  }

  get value() {
    return this._get()
  }
  
  set value(newValue) {
    this._set(newValue)
  }
}
function customRef(factory) {
  return new CustomRefImpl(factory)
}

结语

这次看源码有了写意外收获