Ref全家桶

# Ref全家桶

描述
ref 包装响应式数据
isRef 判断是否为Ref对象
Ref 接口类型
shallowRef 浅层响应式
triggerRef 和shallowRef搭配使用,触发更新
customRef 自定义Ref

# 1. ref

ref返回的是一个响应式对象,看看源码:

直接调用了createRef把值传入并传入false

export function ref(value?: unknown) {
  return createRef(value, false)
}

看看createRef(),先判断是不是Ref对象,是的话直接返回,不是就实例化一个RefImpl对象。

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  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) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

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

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

toReactive,判断值是否为对象,如数组,Object,如果是就用reactive包装一下,不是就直接返回原值。

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value

# 2. isRef

# 3. Ref

# 4. shallowRef

# 5. triggerRef

function triggerRef(ref: ShallowRef): void

shallowRef搭配使用

const shallow = shallowRef({
  greet: 'Hello, world'
})

// Logs "Hello, world" once for the first run-through
watchEffect(() => {
  console.log(shallow.value.greet)
})

// This won't trigger the effect because the ref is shallow
shallow.value.greet = 'Hello, universe'

// Logs "Hello, universe"
triggerRef(shallow)

# 6. customRef

这个函数接收一个工厂函数,该工厂函数必须返回一个拥有getter和setter的对象,工厂函数有两个函数参数,第一个函数用于搜集依赖,第二个函数用于触发更新。

function MyRef<T>(value: T) {
  return customRef((trank, trigger) => {
    return {
      get() {
        trank()
        return value
      },
      set(newValue: T) {
        console.log('set');
        value = newValue
        trigger() // 触发更新
      }
    }
  })
}

const message = MyRef<string>('hhh')

例子:定义一个防抖Ref

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

使用

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

<template>
  <input v-model="text" />
</template>

# shallowRef与ref一起使用造成的问题

# 发生场景

正常情况下更改一个shallowRef对象的属性时,值会更改,但是由于它的浅层响应特性,视图并不会更改,要想更改视图必须调用triggerRef如:

<script setup lang="ts">
import { shallowRef, triggerRef } from 'vue'

const message = shallowRef({
  foo: '球球',
  bar: '闰土'
})

const changeMsg = () => {
  console.log(message);
  triggerRef(message)    // trigger必须传入一个参数
  message.value.foo = 'qiuqiu'
  // triggerRef(message)放在这里效果一样,视图都能更新
}

</script>


<template>
  <div>
    {{ message }}
    <button @click="changeMsg">change shallow</button>
  </div>
</template>


但是如果遇上ref,不用trigger视图也更新了

<script setup lang="ts">
import { shallowRef, ref } from 'vue'

const a = ref('hahaha')


const message = shallowRef({
  foo: '球球',
  bar: '闰土'
})

const changeMsg = () => {
  console.log(message);
  message.value.foo = 'qiuqiu'    // 没有调用trigger视图也更新
  a.value = 'mogutou'    // 这里修改了ref的值,ref的视图更新
}

const changeTwice = () => {
  console.log(message);
  message.value.foo = 'lenmeng'
}

</script>


<template>
  <div>
    {{ a }}
    {{ message }}
    <button @click="changeMsg">change shallow</button>
    <button @click="changeTwice">changeTwice</button>
  </div>
</template>


# 问题原因(源码)

RefImpl类的setter中调用了triggerRefValue,导致shallowRef也更新了。

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