2024-03-01
Vue
0
请注意,本文编写于 79 天前,最后修改于 76 天前,其中某些信息可能已经过时。

目录

shallowRef 与 shallowReactive
readonly 与 shallowReadonly
readonly
shallowReadonly
toRaw 与 markRaw
toRaw
markRaw
customRef

shallowRef 与 shallowReactive

通过使用 shallowRef()shallowReactive() 来绕开深度响应。浅层式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。

vue
<script setup lang="ts"> import { shallowReactive, shallowRef } from 'vue'; let count = shallowRef(0); let person = shallowRef({ name: '张三', age: 18 }); let car = shallowReactive({ brand: '奔驰', options: { color: 'red', engine: 'V8' } }); function changeCount() { count.value += 1; } function changeName() { person.value.name = '李四'; } function changeAge() { person.value.age += 1; } function changePerson() { person.value = { name: 'tony', age: 100 }; } function changeBrand() { car.brand = '保时捷'; } function changeColor() { car.options.color = 'black'; } function changeEngine() { car.options.color = 'V12'; } function changeCar() { Object.assign(car, { brand: '埃文塔多', options: { color: 'yellow', engine: 'V10' } }); } </script> <template> <div> <h2>计数:{{ count }}</h2> <h2>姓名:{{ person.name }}</h2> <h2>年龄:{{ person.age }}</h2> <button @click="changeCount">count + 1</button> <button @click="changeName">修改姓名</button> <button @click="changeAge">修改年龄</button> <button @click="changePerson">修改整个人</button> <h2>{{ car }}</h2> <button @click="changeBrand">修改品牌</button> <button @click="changeColor">修改颜色</button> <button @click="changeEngine">修改发动机</button> <button @click="changeCar">修改整台车</button> </div> </template>

GIF 2024-3-3 21-40-59.gif

readonly 与 shallowReadonly

readonly

创建一个对象的深只读副本,对于 refreactive 都生效

特点:

  • 对象的所有嵌套属性都将变为只读
  • 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)
  • 原对象更改,readonly 对象同步更改

应用场景:

  • 创建不可变的状态快照
  • 保护全局状态或配置不被修改
vue
<script setup lang="ts"> import { readonly, ref, shallowReactive, shallowRef } from 'vue'; let count1 = ref(0); let count2 = readonly(count1); function addCount1() { count1.value += 1; } function addCount2() { count2.value += 1; } </script> <template> <div> <h2>当前count1为:{{ count1 }}</h2> <button @click="addCount1">count1 + 1</button> <h2>当前count2为:{{ count2 }}</h2> <button @click="addCount2">count2 + 1</button> </div> </template> <style scoped></style>

image-20240304200339943.png

GIF 2024-3-4 20-04-56.gif

shallowReadonly

  1. 作用:与 readonly 类似,但只作用于对象的顶层属性

  2. 用法:

    js
    const original = reactive({ ... }); const shallowReadOnlyCopy = shallowReadonly(original);
  3. 特点:

    • 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的
    • 适用于只需保护对象顶层属性的场景

toRaw 与 markRaw

toRaw

用于获取一个响应式对象( reactive()readonly()shallowReactive() 或者 shallowReadonly() )的原始对象,toRaw 返回的对象不再是响应式的,不会触发视图更新

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象

typescript
import { reactive, toRaw } from 'vue'; let person = reactive({ name: 'Morales', age: 23 }); console.log('person', person); // 响应式对象 console.log('toRaw(person)', toRaw(person)); // 原始对象

markRaw

标记一个对象,使其永远不会变成响应式的

例如,使用mockjs时,为了防止误把mockjs变为响应式对象,可以使用 markRaw 去标记mockjs

typescript
let car = markRaw({ brand: 'BMW M3 GTR', price: 150 }); let car2 = reactive(car); console.log('car', car) console.log('car2', car2)

谨慎使用

markRaw() 和类似 shallowReactive() 这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:

  • 有些值不应该是响应式的,例如复杂的第三方类实例或 Vue 组件对象。
  • 当呈现带有不可变数据源的大型列表时,跳过代理转换可以提高性能。

这应该是一种进阶需求,因为只在根层能访问到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:

typescript
const foo = markRaw({ nested: {} }) const bar = reactive({ // 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有 nested: foo.nested }) console.log(foo.nested === bar.nested) // false

识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行逻辑控制

vue
<script setup lang="ts"> import { customRef, ref } from 'vue'; // 使用 Vue 提供的默认 ref 定义响应式数据,数据变化页面随即更新 // let msg = ref('你好') // 使用 Vue 提供的 customRef 定义响应式数据 let initValue = '你好' let msg = customRef((track, trigger) => { // track 跟踪,trigger 触发 return { // msg 被读取时调用 get() { console.log('get'); track(); // 让 Vue 对 msg 进行追踪 return initValue; }, // msg 被修改时调用 set(value) { console.log('set', value); initValue = value; trigger(); // 通知 Vue 数据 msg 发生变化 } } }) </script> <template> <div> <h2>{{ msg }}</h2> <input type="text" v-model="msg"> </div> </template>

将自定义 ref 封装为 hooks,并实现输入时防抖:

useMsgRef.ts

typescript
import { customRef } from "vue"; export default function (initValue: string, delay: number) { let msg = customRef((track, trigger) => { // track 跟踪,trigger 触发 let timer: number; return { // msg 被读取时调用 get() { console.log('get'); track(); // 让 Vue 对 msg 进行追踪 return initValue; }, // msg 被修改时调用 set(value) { clearTimeout(timer); timer = setTimeout(() => { console.log('set', value); initValue = value; trigger(); // 通知 Vue 数据 msg 发生变化 }, delay); } } }) return { msg }; }

App.vue

vue
<script setup lang="ts"> import useMsgRef from './hooks/useMsgRef'; let { msg } = useMsgRef(initValue, 1000); </script> <template> <div> <h2>{{ msg }}</h2> <input type="text" v-model="msg"> </div> </template>

GIF 2024-3-4 22-30-41.gif

本文作者:Morales

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!