Vue3系列(一)Composition Api之Ref系列(附源码解析)

253 阅读3分钟

ref

接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。

import { ref } from 'vue'
const name = ref('小张')

const change = () => {
    // 修改name 页面会响应式修改
    name.value = '小张change'
}

当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value

<template> 
    <button @click="increment"> {{ count }} <!-- 无需 .value --> </button> 
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
const increment = () => { 
    count.value++ 
} 
</script> 

shallowRef

用来创建一个浅层响应式对象;如果是复杂数据类型,只有根级别的属性是响应式的

import { ref, isRef, shallowRef, triggerRef, customRef } from 'vue'
// isRef 判断是否为ref对象
// ref 深层次  shallowRef 浅层次的响应
const man1 = ref({ name: '小张' })

const man2 = shallowRef({ name: '小李' })

const change = () => {
    // 修改man1 页面会响应式修改
    man1.value.name = '小张change'
    
    // 页面不会修改
    man2.value.name = '小李change'
    // 页面会响应式修改
    man2.value = { name: '小李change' }
    
    
    // 同时修改ref 和 shallowRef 页面会同步响应
    man1.value.name = '小张change'
    man2.value.name = '小李change'
    
    // 注意: ref 和 shallowRef 不能一起写 会影响shallowRef 造成视图更新
    
}

ref 与 shallowRef 源码解析

源码路径: /packages/reactivity/src/ref.ts

// ref
// 通过函数重载,支持传入多种类型
export function ref<T extends Ref>(value: T): T
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value, false) // ref 传入false代表需要通过reactive做深层次的响应
}
// shallowRef
// 通过函数重载,支持传入多种类型
export function shallowRef<T extends object>(
  value: T
): T extends Ref ? T : ShallowRef<T>
export function shallowRef<T>(value: T): ShallowRef<T>
export function shallowRef<T = any>(): ShallowRef<T | undefined>
export function shallowRef(value?: unknown) {
  return createRef(value, true) // shallowRef 传入true 代表只需要做浅层响应 只到.value
}

createRef

function ## createRef(rawValue: unknown, shallow: boolean) {
  // 如果以及是一个ref对象,直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  // 不是的话就通过 RefImpl 创建一个ref对象
  return new RefImpl(rawValue, shallow)
}

RefImpl

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

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

  constructor(value: T, public readonly __v_isShallow: boolean) {
    // 通过 __v_isShallow 判断是否通过shallowRef调用, 如果是的话直接返回
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value) // 这里使用toReactive去对引用类型数据创建响应式对象
  }

  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)
    }
  }
}

isRef

用来判断是不是一个ref对象

import { ref, Ref, isRef } from 'vue'

const name: Ref<string | number> = ref('小张')
const notRef: number = 123

const checkRef = () => {
  name.value = "change name"
  
  console.log(isRef(name)); // true
  
  console.log(isRef(notRef)); // false
}

triggerRef

强制更新页面DOM

// 调用此方法进行强制更新, ref底层已调用过
triggerRef()

customRef

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

customRef 是个工厂函数要求我们返回一个对象, 这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

// 自定义hook防抖函数
function useMyRef<T>(value: T) {
 let timer:any
 
  return customRef((track, trigger) => {
    return {
      // 返回数据
      get() {
          // 追踪数据
        track()
        return value
      },
      // 设置数据
      set(newVal) {
        clearTimeout(timer)

        timer = setTimeout(() => {
          console.log('触发了');
          value = newVal
          timer = null
          trigger()
        }, 500)
        
      }
    }
  })
}

const obj = MyRef<string>('校长')

const handleChange = () => {
  obj.value = '我是小张'
}

通过ref获取dom属性

<template>
    <button @click="handleChange">获取dom</button>
​
    <div ref="dom">我是dom</div>
​
</template>
import { ref } from 'vue'const dom = ref<HTMLElement>()
​
const handleChange = () => {
  console.log(dom.value?.innerHTML)
}

unref

如果参数是 ref,则返回内部值,否则返回参数本身。

import { ref } from 'vue'

const dom = ref<Number>(1)

unref(val)
// 相当于下面的语法糖
val = isRef(val) ? val.value : val