Vue3源码解析--ref

本编文章,我们一起学习Vue3中响应式API部分ref相关的源码实现,只有更深的掌握了相关的实现原理和技术细节,才能让我们在项目的开发应用中更好的使用框架来进行开发。

1. ref

官方描述: 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value

1.1 ref出现的目的

在vue2中,我们所有的响应式数据全部定义在组件data属性上,即使现在用不到,将来需要使用,也要先被定义;而在vue3中,响应式模块全部进行了重写,新增组合式API setup(),用来提升在vue2中的选项式API组件代码风格的不足,而在vue3中新增响应式API reactive()函数,主要用来处理Object类型数据,而有时候我们组件中需要一个简单值为响应式,如number、string等,这时候,就非常适合用ref来处理响应式。

1.2 源码实现

文件定义:packages\reactivity\src\ref.ts

export function ref(value?: unknown) {
  return createRef(value)
}
复制代码

ref函数定义非常简单

说明:

  1. ref被定义为一个函数导出
  2. 返回调用creatRef()

creatRef()函数源码实现:

function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    // 已经是ref代理对象,返回原目标对象
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
复制代码

说明:

  1. createRef接受两个参数,rawValue原始值,shallow浅代理标志,默认为false
  2. 首先判断传入的值是否为ref类型,如果是ref类型值,则直接返回传入值,不做任何处理
  3. 如果是非ref类型,则返回调用RefImpl类的实例对象

RefImpl类的实现:

class RefImpl<T> {
  private _value: T
  // ref 标识,isRef判断依据
  public readonly __v_isRef = true

  constructor(private _rawValue: T, public readonly _shallow = false) {
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }
  // 获取数据
  get value() {
    // 依赖收集
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }
  // 设置新值
  set value(newVal) {
    // 判断新旧值是否有变化
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      // 触发依赖
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}
复制代码

说明:

  1. ref的响应式在这里使用了es6中class类的get set属性进行代理
  2. RefImpl类定义了私有属性_value,用来保存旧值,只读属性__v_isRef默认为true,用以isRef类型判断
  3. 构造函数constructor接受两个参数,原始值和是否为浅响应标志
  4. 构造函数内部根据_shallow进行保存旧值,浅代理则直接保存原始值,否则保存调用covert()函数的返回值
  5. get获取数据进行收集依赖
  6. set设置新值,首先判断新旧两个值是否有变化,没有变化则直接返回,不进行触发依赖操作,此为性能优化方式,新旧两个值有变化,则进行修改值并保存,触发依赖更新渲染。

convert()函数的实现:

const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val
复制代码

在这里我们看到convert函数内部通过进行判断传入值的类型分别处理,如果为Object类型数据则通过调用reactive()函数获取值,简单值则返回值本身。

1.3 ref相关总结
  1. 对于简单值的响应式处理则使用es6中class get set进行拦截,而复杂类型数据其还是通过reactive()实现
  2. 修改新值,会进行新旧值的变化判断是否更新依赖,进行性能优化

2. shallowRef

官方描述:创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的

解释说明:shallowRef只会是其.value自身为响应式,.value对应的值不会进行响应式处理

2.1 源码实现
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
复制代码

说明:

  1. shallowRef的实现与ref实现基本一致,不同表现为在调用createRef时候,将shalloa值写为true

3. isRef

官方描述:检查值是否为一个 ref 对象

3.1 源码实现
export function isRef(r: any): r is Ref {
  // 通过__v_isRef属性判断一个对象是否是ref对象
  return Boolean(r && r.__v_isRef === true)
}
复制代码

检查判断很简单,就是通过RefImpl类内部属性进行判断

4. unRef

官方描述:如果参数是一个 ref,则返回内部值,否则返回参数本身。

4.1 源码实现
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
  return isRef(ref) ? (ref.value as any) : ref
}
复制代码

内部实现依然很简单,通过判断传入值,如果是ref则返回ref.value值本身,否则返回传入值。

5. toRef

官方描述:可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。

5.1 源码实现
export function toRef<T extends object, K extends keyof T>(
  object: T,
  key: K
): ToRef<T[K]> {
  return isRef(object[key])
    ? object[key]
    : (new ObjectRefImpl(object, key) as any)
}
复制代码

说明:

  1. 函数接收两个参数,object和key
  2. 首先判断object[key]是否为ref类型,是则直接返回object[key]对应值,否则实例化类ObjectRefImpl,创建一个ref并返回

ObjectRefImpl类的实现

class ObjectRefImpl<T extends object, K extends keyof T> {
  // 只读ref类型属性
  public readonly __v_isRef = true
  constructor(private readonly _object: T, private readonly _key: K) {}

  get value() {
    return this._object[this._key]
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }
}
复制代码

6. toRefs

官方描述:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

6.1 源码实现
export function toRefs<T extends object>(object: T): ToRefs<T> {
  if (__DEV__ && !isProxy(object)) {
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret: any = isArray(object) ? new Array(object.length) : {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}
复制代码

说明:

  1. 首先判断传入object参数是否为代理对象,要求object参数为代理对象
  2. 判断object参数是否为数组,为数组则创建一个长度为object.length的空数组,否则即为Object类型,则初始化一个空对象
  3. 遍历object,并调用toRef(object,key)
  4. 返回对象或者数组类型结果格式如下:
[ref, ref, ref]
or
{
    key1: ref,
    key2: ref
}
复制代码

猜你喜欢

转载自juejin.im/post/7034043016188788744