「Vue3学习篇」-shallowRef()、triggerRef()和customRef()

『引言』

本篇介绍shallowRef()、triggerRef()和customRef()这3个API。关于这3个API是用来干什么的,以及使用方法等等都会有说明。

『shallowRef()』

『定义』

【官方解释】ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

shallowRef() 常常用于对大型数据结构的性能优化或是与外部的状态管理系统集成。

【我的理解】shallowRef()是浅层响应式数据,当只对.value整体值修改时视图会更新。但当修改更深层次的属性变化时,视图不会更新。

简而言之就是第一层是响应式数据,非第一层数据不是响应式数据。

『shallowRef()用法』

ini 复制代码
const state = shallowRef({ count: 1 }) 
// 会触发更改 
state.value = { count: 2 }
// 不会触发更改 
state.value.count = 2 

『shallowRef()注意』

  • ref 和 shallowRef 不能同时使用,会影响shallowRef造成视图更新。
  • 只有第一层数据变了组件才会重新渲染,深层数据变了组件不会重新渲染,watch也监听不到变化。
  • shallowRef()它仅仅针对于.value数据的变化。在修改shallowRef()创建数据的时候,应该采用XXX.value = XXX的形式。

『示例1🌰:使用shallowRef()修改基本数据类型』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{hobbies.join('、')}}</p>
    <p>描述:{{description}}</p>
    <button @click="modifyInfo">
    点击按钮修改信息
    </button> 
  </div> 
</template>

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

    const name = shallowRef('pupu')
    const age = shallowRef(10)
    const hobbies = shallowRef(['唱歌', '画画'])
    const description = shallowRef('一点也不可爱,不喜欢吃蜂蜜!')
    
    // 修改信息
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        hobbies.value = ['打羽毛球', '旅游']
        description.value = '非常的可爱,特别喜欢吃蜂蜜!'
    }     
</script>

『效果展示』

点击修改按钮之后,页面信息更新。

『示例2🌰:使用shallowRef()修改具体属性』

xml 复制代码
<template>
  <div>
    <h1>最想去的地方</h1>
    <p>地址:{{address.provice}} - {{ address.city }} </p>
    <button @click="modifyInfo">
    修改信息
    </button> 
  </div> 
</template>

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

    const address = shallowRef({
      provice: '浙江省',
      city: '杭州市'
    })
    
    // 修改
    const modifyInfo = () => {
        address.value.provice = '云南省'
        address.value.city = '丽江市'
        console.log(address, '修改address')
    } 
</script>

『效果展示』

通过打印信息可以看到,数据已经更新,但视图却未更新。

『示例3🌰:使用shallowRef()修改对象』

👇通过下面这个示例,探索一下为什么使用shallowRef()会出现,数据更新,但视图并未更新的原因。

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{ info.address.city }} </p>
    <p>描述:{{info.description}}</p>
    <button @click="modifyInfo">
    点击按钮修改信息
    </button> 
  </div> 
</template>

<script setup>
import { shallowRef } from 'vue'
    
    const info = shallowRef({
      name: 'pupu',
      age: 10,
      hobbies: ['唱歌', '画画'],
      address: {
        provice: '浙江省',
        city: '杭州市',
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!'
    })
    console.log(info, 'info未改')

    const modifyInfo = () => {
        info.value.name = 'wnxx'
        info.value.age = 3 
        info.value.hobbies = ['打羽毛球', '旅游']
        info.value.address.provice = '云南省'
        info.value.address.city = '丽江市'
        info.value.description = '非常的可爱,特别喜欢吃蜂蜜!'
        console.log(info, 'info修改')
    }  
</script>

『效果展示』

首先,可以看到点击按钮之后,视图并没有任何的变化,展示的还是原视图的信息。

接着再从打印信息的值来看是有变化的,橘色框中未点击按钮的原始值与绿色框中点击按钮之后的值进行对比,值是改变了。打印信息有变化,但视图仍处于未更新状态。

那究竟是为什么呢❓🤔️

找一找原因,来看看源码,在源码中使用shallowRef()定义了一个名为info的对象。在modifyInfo()函数中处理的都只是针对info这个对象的非第一层数据来操作的。

shallowRef()只会对对象的第一层进行响应式处理,当对象的更深层次的属性变化时,不会触发组件重新渲染,容易出现更新不及时的情况。

『示例4🌰:shallowRef()在某些情况下执行属性值修改时,同步更新到视图层』

再继续讲一种情况,那就是当我们对对象的非第一层数据进行修改时,视图也跟着更新了的情况。

这种情况也是存在的,具体是怎么个情况,来继续往下看,先上源码📝。

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{hobbies.join('、')}}</p>
    <p>地址:{{address.provice}} - {{ address.city }} </p>
    <p>描述:{{description}}</p>
    <button @click="modifyInfo">
    修改信息
    </button> 
  </div> 
</template>

<script setup>
import { ref, shallowRef } from 'vue'
    const name = ref('pupu')
    const age = ref(10)
    const hobbies = ref(['唱歌', '画画'])
    const address = shallowRef({
      provice: '浙江省',
      city: '杭州市'
    })
    const description = shallowRef('一点也不可爱,不喜欢吃蜂蜜!')
    
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        hobbies.value = ['打羽毛球', '旅游']
        address.value.provice = '云南省'
        address.value.city = '丽江市'
        description.value = '非常的可爱,特别喜欢吃蜂蜜!'
    }   
</script>

『效果展示』

显示效果如下:

这种情况就是shallowRef和ref赋值同时出现,shallowRef会受到影响

『ref 和 shallowRef 不能同时使用,会影响shallowRef造成视图更新』

这是怎么一回事呢❓🤔

我们看一看,👇下面这一段源码。

typescript 复制代码
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)
}

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

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

分析一下这段源码是怎么回事。

首先调用shallowRef方法,调用shallowRef方法时将shallow设为true,然后会调用createRef方法,接着进入RefImpl。

这里要提一下。

sql 复制代码
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,最后也会调用createRef方法,进入RefImpl。

也就是说不管是ref还是shallowRef,都会调用createRef方法,进入RefImpl。

继续看一下RefImpl,在这里都会调用triggerRefValue方法。triggerRefValue方法会使更新视图。

因此,原因就是,在代码中name,age等属性修改的时候,触发了ref更新,ref底层更新逻辑的时候,会调用triggerRefValue方法更新视图,导致shallowRef的数据也更新。

『示例5🌰:shallowRef()在某些情况下执行属性值修改时,同步更新到视图层』

最最最......后,再说一下这种情况的出现。

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}岁</p>
    <p>爱好:{{hobbies.join('、')}}</p>
    <p>地址:{{address.provice}} - {{ address.city }} </p>
    <p>描述:{{description}}</p>
    <button @click="modifyInfo">
    修改信息
    </button> 
    <button @click="repeatModifyInfo">
    点击按钮修改信息
    </button> 
  </div> 
</template>

<script setup>
import { shallowRef } from 'vue'
    const name = shallowRef('pupu')
    const age = shallowRef(10)
    const hobbies = shallowRef(['唱歌', '画画'])
    const address = shallowRef({
      provice: '浙江省',
      city: '杭州市'
    })
    const description = shallowRef('一点也不可爱,不喜欢吃蜂蜜!')
    // 第一次修改
    const modifyInfo = () => {
        name.value = 'wnxx'
        age.value = 3 
        hobbies.value = ['打羽毛球', '旅游']
        address.value.provice = '云南省'
        address.value.city = '丽江市'
        description.value = '非常的可爱,特别喜欢吃蜂蜜!'
        console.log(address, '第一次修改address')
    } 
    // 第二次修改
    const repeatModifyInfo = () => {
      name.value = 'Ljylwnxx'
      setTimeout(() => {
          address.value.provice = '广西省'
          address.value.city = '桂林市'
        }, 10)
      console.log(address, '第二次修改address')  
    }
</script>

提问🚩:点击修改信息和点击按钮修改信息之后,视图会发生怎样的变化❓🤔🤔

答案📒:点击【修改信息】按钮🔘,视图中所有信息都会更新;点击【点击按钮修改信息】按钮🔘,视图中姓名信息会更新,但地址信息不会更新。

『第一次点击🔘:』

我们先来看一下,第一次点击按钮之后的视图变化效果以及打印信息的情况。

『效果展示』

从图片中可以清楚的看到所有信息都已更新。

提问🚩:为什么视图中地址信息也更新了?

答案📒:第一次修改信息的时候,会重新触发render,在本次render就导致所有信息都更新。

『第二次点击🔘:』

『效果展示』

提问🚩:为什么视图中地址信息未更新?

答案📒:第二次修改信息的时候,也会重新触发render,姓名信息就已更新,地址信息未更新。

这是由于地址信息的修改是setTimeout,未在本次render内,所以视图中地址信息未更新,但打印信息中是已更新的。

『triggerRef()』

『前言』

问题1🚩:在shallowRef()中提到,ref和shallowRef()不能同时使用,ref会影响shallowRef导致视图也更新。

问题2🚩:在shallowRef()中,修改时要将整个 .value 重新赋值,视图会更新。这样感觉操作很麻烦。

问题3🚩:shallowRef()在修改对象值时,无法实现视图更新。

解决这些问题的办法就是要使用到一个 API,叫做 triggerRef。

『定义』

【官方解释】强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。

【我的理解】配合shallowref()一起使用,强制触发响应,手动更新视图。

『用法』

接收一个参数 state ,即需要更新的 ref 对象。

triggerRef(state)

『示例🌰』

xml 复制代码
<template>
  <div>
    <h1>人物简介</h1>
    <p>姓名:{{info.name}}</p>
    <p>年龄:{{info.age}}岁</p>
    <p>爱好:{{info.hobbies.join('、')}}</p>
    <p>地址:{{info.address.provice}} - {{ info.address.city }} </p>
    <p>描述:{{info.description}}</p>
    <button @click="modifyInfo">
    点击按钮修改信息
    </button> 
  </div> 
</template>

<script setup>
import { shallowRef, triggerRef } from 'vue'
    
    const info = shallowRef({
      name: 'pupu',
      age: 10,
      hobbies: ['唱歌', '画画'],
      address: {
        provice: '浙江省',
        city: '杭州市',
      },
      description: '一点也不可爱,不喜欢吃蜂蜜!'
    })

    const modifyInfo = () => {
        info.value.name = 'wnxx'
        info.value.age = 3 
        info.value.hobbies = ['打羽毛球', '旅游']
        info.value.address.provice = '云南省'
        info.value.address.city = '丽江市'
        info.value.description = '非常的可爱,特别喜欢吃蜂蜜!'
        triggerRef(info)
    }  
</script>

『效果展示』

视图由原来的不会更新,到视图更新了。

『customRef()』

『定义』

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

customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

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

【我的理解】customRef用于自定义ref,customRef()有两个函数作为参数,track用于追踪数据,trigger 用于触发响应,更新视图。track 方法放在 get 中,trigger 方法放在 set 中。

『customRef()用法』

javascript 复制代码
import { customRef } from 'vue';

// customRef用于自定义XXXRef
function XXXRef(value) {
    // 该函数默认带参数 track 和 trigger 两个方法。
    return customRef((track, trigger) => {
      // customer 需要提供一个对象作为返回值
      return {
        // 该对象需要包含 get 和 set 方法。
        get() {
          // 追踪数据变化
          track()
          return value
        },
        // set 传入一个值作为新值,通常用于取代value
        set(newValue) {
          value = newValue
          // 触发响应,更新视图
          trigger()
        }
      }
    })
}

『官网示例🌰』

创建一个防抖 ref,只在最近一次set调用后的一段固定间隔后再调用

相关推荐
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王2 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发2 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪3 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef4 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6415 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云5 小时前
npm淘宝镜像
前端·npm·node.js