Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/ref.ts

前言

上一篇《Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/reactive.ts》我们学习了使用 Proxy 实现的 reactive 构造出来的响应式的对象,但 reactive 接受的值的参数限于 Object, Array, Map, Set, WeakMap, WeakSet 这些类型,而本篇介绍的 refreactive 也类似,但可以接收任何类型的值的参数。而且对值的监听的实现方式也不一样,使用 get set 的方式对值实现拦截监听,然后在 get 操作时添加 track,在 set 操作时调用 trigger,从而达到响应式的效果。

源码分析

注意带【】符号的为关键代码,关键代码能体现作者的最关键最基础的实现思路

import { track, trigger } from './effect' // track 了之后就会把 effect add 到 dep 中,然后再把 dep push 到 effect.deps里,trigger 顾名思义就是触发相关的所有 effects 事件的执行
import { OperationTypes } from './operations' // set, add, delete, clear, get, has, iterate 操作类型
import { isObject } from '@vue/shared'
import { reactive } from './reactive' // 上篇介绍的 reactive 构造函数,用于生成一个被 proxy 的代理对象
import { ComputedRef } from './computed' // 顾名思义,应该就是 与 Vue 2.x computed 类似的 Ref

export interface Ref<T = any> { // 把所有值转化成一个 Object 类形,其值藏于解套 UnwrapRef 后的 value 中
  _isRef: true
  value: UnwrapRef<T>
}

const convert = (val: any): any => (isObject(val) ? reactive(val) : val) // 如果值为 Object,就直接转成 Reactive 对象

export function ref<T extends Ref>(raw: T): T
export function ref<T>(raw: T): Ref<T>
export function ref(raw: any) { // ref 构造函数
  if (isRef(raw)) {
    return raw
  }
  raw = convert(raw)
  const v = { // 【Ref 的关键代码】:把 raw 值使用 get set 转化成一个 watchable 的 Object
    _isRef: true,
    get value() { // get 操作需要 track
      track(v, OperationTypes.GET, '')
      return raw
    },
    set value(newVal) { // set 操作需要 trigger
      raw = convert(newVal)
      trigger(v, OperationTypes.SET, '')
    }
  }
  return v as Ref
}

export function isRef(v: any): v is Ref { // 是否为 Ref 类型呢?v._isRef === true 就是
  return v ? v._isRef === true : false
}

export function toRefs<T extends object>( // 把 object (K, T[K]) 对像转化成另外一个 key 相同为 K 但值为 Ref<T[K]> 的 Object
  object: T
): { [K in keyof T]: Ref<T[K]> } {
  const ret: any = {}
  for (const key in object) {
    ret[key] = toProxyRef(object, key)
  }
  return ret
}

function toProxyRef<T extends object, K extends keyof T>( // 把 T[K] 变成 Ref<T[K]>
  object: T,
  key: K
): Ref<T[K]> {
  return {
    _isRef: true,
    get value(): any {
      return object[key]
    },
    set value(newVal) {
      object[key] = newVal
    }
  }
}

type BailTypes =
  | Function
  | Map<any, any>
  | Set<any>
  | WeakMap<any, any>
  | WeakSet<any>

// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = { // 神奇的递归解套,UnwrapRef({a: {b: {c: 1}}}}) => {1删object: {2删a : {3删object: {4删b: {5删object: {6删c: {7删ref: 1}}}}}}} => 1
  cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  object: { [K in keyof T]: UnwrapRef<T[K]> }
}[T extends ComputedRef<any>
  ? 'cRef'
  : T extends Ref
    ? 'ref'
    : T extends Array<any>
      ? 'array'
      : T extends BailTypes
        ? 'ref' // bail out on types that shouldn't be unwrapped
        : T extends object ? 'object' : 'ref']

总结

要实现数据的响应式的绑定,实现的思路有很多,如 Vue 2.x 使用的是 Object.definedProperty 的方式。而到了 Vue 3 则大胆使用 ES6 的 Proxyreactive 的实现) 及 get setRef的实现)。还有不得不说,本篇源码中的 typescript 的神奇的递归解套真是非常精彩。真是应了那句:Talk is cheap, show me the code!

作者: 博主

Talk is cheap, show me the code!

发表评论

邮箱地址不会被公开。

Captcha Code