vue3源码ref章节

Ref类型

// 生成一个唯一key,开发环境下增加描述符 'refSymbol'
declare const RefSymbol: unique symbol
export interface Ref<T = any> {
  /**
   * Type differentiator only.
   * We need this to be in public d.ts but don't want it to show up in IDE
   * autocomplete, so we use a private Symbol instead.
   */
  [RefSymbol]: true
  // 之前这个类型是UnwrapNestedRefs<T>,这样会造成Ref类型套Ref类型
  // 像改成现在这种,最外层最多只有一层Ref,避免需要写多次value
  // export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
  // 之前迭代的代码
  value: T
}
// ToRefs方法主要将对象第一层的value都变成Ref类型
export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }
// 判断是否是Ref数据的方法
// is是给一个提示,函数用来判断r是否是Ref
// 如果使用isRef<string>(ref('')),则进入第一个function函数
// 如果使用isRef(ref('')),则进入第二个function函数
// 返回值不同,第一个是r is Ref<T>,第二个是r is Ref
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
  return r ? r.__v_isRef === true : false
}

Ref类型主要有两个字段,1.RefSymbol字段,唯一标识属性,用来判断是否为Ref类型;2.value字段为T类型(泛型),举个例子:Ref,则value值为string类型。isRef声明了两个函数,通过不同方式调用使用不同的函数,只是公用函数逻辑,返回值的提示不同。

UnwrapRef

// 解构T类型,如果是Ref<V>类型进行则是结构Ref,变成UnwrapRefSimple<V>
// 其余类型直接UnwrapRefSimple<T>
// 从以上可了解到主要是将Ref类型解套,去除掉Ref
export type UnwrapRef<T> = T extends Ref<infer V>
  ? UnwrapRefSimple<V>
  : UnwrapRefSimple<T>
type BaseTypes = string | number | boolean
// CollectionTypes指Map、Set、WeakMap、WeakSet类型
// 如果是Funcion、Map、Set、WeakMap、WeakSet、Ref、string、number、boolean
// 则直接保持原类型,数组与对象继续递归解构
type UnwrapRefSimple<T> = T extends
  | Function
  | CollectionTypes
  | BaseTypes
  | Ref
  | RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
  ? T
  : T extends Array<any>
    ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
    : T extends object ? UnwrappedObject<T> : T
// Extract all known symbols from an object
// 这地方用到了交叉类型,使对象类型覆盖更全
type UnwrappedObject<T> = { [P in  keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

UnwrapRef主要用来解构Ref类型,如果是UnwrapRef<Ref> ==> UnwrapRefSimple ===> string,最终结果将Ref类型进行了解构。UnwrapRefSimple主要是递归进行解构数据解构类型,普通类型直接返回,数组与对象则递归遍历其中所有项进行获取类型。上述有个问题,针对Array是没有进行处理的,因此数组中调用ref数据必须一层层调用,之前版本有过处理,这版本没有,有待调查。

SymbolExtract

// Extract all known symbols from an object
// when unwrapping Object the symbols are not `in keyof`, this should cover all the
// known symbols
type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
  ? { [Symbol.asyncIterator]: V }
  : {}) &
  (T extends { [Symbol.hasInstance]: infer V }
    ? { [Symbol.hasInstance]: V }
    : {}) &
  (T extends { [Symbol.isConcatSpreadable]: infer V }
    ? { [Symbol.isConcatSpreadable]: V }
    : {}) &
  (T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
  (T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
  (T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
  (T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
  (T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
  (T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
  (T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
  (T extends { [Symbol.toPrimitive]: infer V }
    ? { [Symbol.toPrimitive]: V }
    : {}) &
  (T extends { [Symbol.toStringTag]: infer V }
    ? { [Symbol.toStringTag]: V }
    : {}) &
  (T extends { [Symbol.unscopables]: infer V }
    ? { [Symbol.unscopables]: V }
    : {})

部分Object的key可能不能通过in keyof获取,这里是用来涵盖这部分key对应value的类型。其中&为交叉类型作用,上述作用就是object中的类型可能为{ [P in keyof T]: UnwrapRef<T[P]> }、{}、{ [Symbol.unscopables]: V }的合并类型,拥有上述三种所有类型的属性。

ShallowUnwrapRef

export type ShallowUnwrapRef<T> = {
  [K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]
}

上述方法同样是为了解构,将包含Ref类型对象与数组进行一层解构。

Ref函数

// 下面四个ref函数主要针对调用方式不同而进行不同的调用,
// 但是都会触发createRef(value)
// 针对四个函数的作用是针对不同情况进行对返回值不同类型处理
// 如果使用T为对象,则返回T或Ref<UnwrapRef>

export function ref<T extends object>(
  value: T
): T extends Ref ? T : Ref<UnwrapRef<T>>
// 调用加了类型,因为不满足上述条件,则一定不是对象,一定不是Ref类型
// 直接返回类型为Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
// 如果加了类型但是没有传参,返回类型为Ref<T | undefined>
export function ref<T = any>(): Ref<T | undefined>
// 如果以上都不满足,兜底函数,不限制入参与返回值
export function ref(value?: unknown) {
  return createRef(value)
}
// shallowRef差不多,不过调用createRef的第二个参数为true
export function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>
export function shallowRef<T = any>(): Ref<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true)
}
function createRef(rawValue: unknown, shallow = false) {
  // 如果是Ref类型则直接返回  
  if (isRef(rawValue)) {
    return rawValue
  }
  // 如果是shallow则简单处理,直接返回
  // 否则调用convert进行使用reactive变成响应式数据
  // shallow只对value本身做代理,而ref是对整个数据使用reactive递归做代理
  let value = shallow ? rawValue : convert(rawValue)
  const r = {
    __v_isRef: true,
    // 这种方式的定义必须通过r.value值进行获取与赋值
    get value() {
      // 获取依赖,effect模块介绍,其实很简单就是effect(watch)与dep,与vue2中的队列方式差不多,只是个映射关系
      track(r, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newVal) {
      // toRaw就是将响应式对象变成原始对象
      // 每次reactive返回的对象都是一个新的对象  
      if (hasChanged(toRaw(newVal), rawValue)) {
        rawValue = newVal
        value = shallow ? newVal : convert(newVal)
        // 触发依赖,effect模块介绍
        trigger(r, TriggerOpTypes.SET, 'value', newVal)
      }
    }
  }
  return r
}
// 判断value与oldValue是否有过更改
export const hasChanged = (value: any, oldValue: any): boolean =>
  value !== oldValue && (value === value || oldValue === oldValue)
// 如果是对象用reactive进行处理,否则直接返回val,由此可看出,reactive主要负责处理复杂数据类型的响应式,ref针对简单数据类型的响应式
// reactive由reactive部分进行讲解
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

上述核心是createRef函数,主要是对value值进行代理,通过对get与set进行收集依赖与触发依赖。
shallowRef主要是对value本身做代理与依赖收集触发,而ref是通过reactive将value变成响应式数据结构。

Ref与shallowRef总结

shallowRef的出现与我前几天思考的一个问题正好关联,我在想,如果前端使用select或后端使用picker实现下拉框操作时,下拉框中的所有项都实现了数据监听,假设有几十个下拉选项,会对几十个下拉选项都做监控,非常影响性能,shallowRef的出现正好可以解决此类,问题,因为他只对value本身做了数据代理,正好可以解决range针对性能造成的问题。

总结

Ref模块主要是针对vue中的类型进行一些定义,主要难点都是typescript的知识点,其余没有什么难点,其中最核心的就是针对Ref嵌套类型的解构,避免深层次调用Ref类型还需要使用value属性进行获取值。ref函数也比较简单,直接通过reactive变成响应式数据,shallowRef主要针对简单响应式数据,依据不同的场景选择不同的函数调用方式,ref函数的总结是我想到一个点,如果有更多欢迎分享。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值