Vue3:简单使用 ref、isRef、shallowRef、triggerRef、customRef

685 阅读4分钟

[vue3源码](GitHub - vuejs/core: 🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.)

ref

import { ref, onMounted } from 'vue';
import type { Ref } from 'vue'

type Q = {
  name: string;
}

// ref
const data: Ref<Q> = ref({ name: "小明" })

const changeData = function () {
  data.value.name = '小张';
  console.log(data.value);
}

const pDom = ref<HTMLElement>()

onMounted(() => {
  console.log(pDom.value?.innerHTML);
  console.log(pDom.value);
})
<div>
    <p ref="pDom"> dom 元素 </p>
    <button @click="changeData">修改</button>
    <div>{{ data.name }}</div>
</div>

效果图:

1676274988708.jpg

isRef

import { ref, isRef } from 'vue';

const data = ref('我是ref数据')

const data2 = { name: "郭嘉", str: "我不是ref数据" }

const _isRef = isRef(data)
const _isRef2 = isRef(data2)
<div>
  <div>data是不是ref数据? -> {{ _isRef? '是': '不是' }}</div>
  <div>data2是不是ref数据? -> {{ _isRef2? '是': '不是' }}</div>
</div>

效果图:

1676275474836.jpg

shallowRef

import { shallowRef } from 'vue';

const data = shallowRef({ name: "刘备" })

const changeData = function () {
  console.log('修改前: ', data.value);
  data.value.name = '曹操'
  console.log('修改后: ', data.value);
}
<div>
    <div>{{ data.name }}</div>
    <button @click="changeData">修改name</button>
</div>

效果图:

3.jpg

通过上面这种写法可以看到,点击'修改name'按钮之后,data中的值虽然发生改变了,但是没有更新视图,需要改成下面这种写法:

import { shallowRef } from 'vue';

const data = shallowRef({ name: "刘备" })

const changeData = function () {
  console.log('修改前: ', data.value);
  data.value = { name: '曹操' } // <===================================================
  console.log('修改后: ', data.value);
}

原因是 shallowRef 只有对.value的访问是响应式的(浅层响应式)

效果图:

1.jpg

triggerRef

import { shallowRef, triggerRef } from 'vue';
const data = shallowRef({ name: "貂蝉" })
const changeData = function () {
  console.log('修改前: ', data.value);
  data.value.name = '陆逊'
  // triggerRef(data)
  console.log('修改后: ', data.value);
}
<template>
  <div>
    <div>{{ data.name }}</div>
    <button @click="changeData">修改</button>
  </div>
</template>

不使用triggerRef效果图:

2.jpg

可以看到,和shallowRef的效果一模一样,使用triggerRef将代码修改为:

import { shallowRef, triggerRef } from 'vue';
const data = shallowRef({ name: "貂蝉" })
const changeData = function () {
  console.log('修改前: ', data.value);
  data.value.name = '陆逊'
  triggerRef(data) // <------------------------
  console.log('修改后: ', data.value);
}

使用triggerRef效果图:

3.jpg

customRef

ref内部实现原理很像,最麻烦的一个,先上代码:

import { customRef } from 'vue'
import type { Ref } from 'vue';
const _Ref = function <T>(value: T): Ref<T> {
  return customRef((track, trigger) => {
    return {
      get() {
        console.log('get方法被调用');

        track()
        return value
      },
      set(newValue) {
        console.log('set方法被调用,新值为: ', newValue);

        value = newValue
        trigger()
      }
    }
  })
}

const data = _Ref({ name: "张辽" })
  <div>
    <div>{{ data.name }}</div>
  </div>

效果图:

4.jpg

现在我们来尝试修改一下,data中的数据:

import { customRef } from 'vue'
import type { Ref } from 'vue';
const _Ref = function <T>(value: T): Ref<T> {
  return customRef((track, trigger) => {
    return {
      get() {
        console.log('get方法被调用');
        track()
        return value
      },
      set(newValue) {
        console.log('set方法被调用,新值为: ', newValue);
        value = newValue
        trigger()
      }
    }
  })
}

const data = _Ref({ name: "张辽" })

const changeData = function () {
  console.log('修改前: ', data.value);
  data.value = { name: '孙权' }
  console.log('修改后: ', data.value);
}
  <div>
    <div>{{ data.name }}</div>
    <button @click="changeData">修改</button>
  </div>

效果图:

5.jpg

注意: 值类型必须保持一致! 上述例子中用的是object类型,新值类型也必须为object

小结

ref: 返回一个响应式数据,可更改的ref对象

isRef: 判断某个值是不是 ref 对象

shallowRef: 浅层响应式,修改值时,不可以和 ref 对象写在一起!

triggerRef: 强制更新收集的依赖

customRef: 自定义 ref

关于 shallowRef 为什么不能和 ref 写在一起:

import { ref, shallowRef } from 'vue'

const refData = ref({ name: "张角" })

const shallowRefData = shallowRef({ name: "董卓" })

const changeData = function () {
  console.log("修改前: ", refData.value);
  console.log("修改前: ", shallowRefData.value);

  refData.value.name = "马超"
  shallowRefData.value.name = "马云禄"

  console.log("修改后: ", refData.value);
  console.log("修改后: ", shallowRefData.value);
}
  <div>
    <div>{{ refData.name }} -- {{ shallowRefData.name }}</div>
    <button @click="changeData">修改</button>
  </div>

效果图:

1676280094206.jpg

上述例子中,我们把修改shallowRefref的值放在了一起,可以看到shallowRef的值同样被修改了(会更新视图),至于为什么会这样,那是因为vue3源码中在ref的内部调用了triggerRef方法

这两个东西,千万不要写在一块!!!

下载好vue3源码之后,文件地址:

ref 源码 : \core-main\packages\reactivity\src\ref.ts

export function ref<T extends object>(
  value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<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)
}

函数重载,去调用createRef方法

createRef方法:

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

这个方法首先判断我们传进来的值,是不是一个ref对象,如果是,就直接return这个对象,如果不是,直接创建ref对象

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

在这里我们可以知道,ref返回的就是一个'类'

其中`_value`就是我们要的值

`__v_isShallow`是我们调用`createRef`方法传递的第二个参数

拿着这个布尔值进行判断,如果为真,返回值本身,如果为假,调用 `toReactive` 方法

toReactive方法:

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value
这个函数会判断我们传入的值是不是一个引用类型

如果是引用类型,那么就调用`reactive`方法

如果不是,直接返回值本身, `reactive`方法后面再写,暂时先不写了

RefImpl 类中的 getset方法跟我们customRef中的很像

源码中shallowRef就是把调用createRef方法的第二个参数置为了true

export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }

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`是不会被调用`toReactive`方法的

响应式只到`.value`

上面说到triggerRef会更新shallowRef的值,在我们看过源码之后,发现set值的时候,是去调用了triggerRefValue方法,而我们使用reftriggerRefset值的时候,其实调用的也是triggerRefValue方法,而这个方法内部会去更新我们的依赖