【涅槃】Vue3学习笔记(四)

239 阅读9分钟

Vue3中shallwRef和shallshallReactive使用

在 Vue 3 中,shallowRefshallowReactive是用于优化性能的响应式 API,它们与ref和 reactive 的主要区别在于它们对嵌套数据的处理方式。

shallowRef

通俗理解 shallowRef是一种特殊的ref,它只会在引用(内存地址)发生变化时触发响应式更新,而不会追踪对象内部属性的变化。

类比

想象你有一个文件夹,文件夹里有很多文件。shallowRef只会关注你是否更换了整个文件夹,而不会关心文件夹内部的文件是否被修改。

特点

  • 只追踪引用变化:只有当你完全替换掉shallowRef的值时,才会触发响应式更新。
  • 不追踪嵌套属性:如果你修改了shallowRef的某个嵌套属性,不会触发响应式更新。

使用场景

  • 性能优化:当你有一个很大的对象,但只需要在完全替换对象时触发更新,可以使用shallowRef
  • 动态组件:例如在切换组件时,你可以用 shallowRef 来存储当前组件的引用。

示例

import { shallowRef } from 'vue';

const state = shallowRef({ count: 1 });

// 不会触发更新
state.value.count = 2;

// 会触发更新
state.value = { count: 2 };

shallowReactive

通俗理解

shallowReactive是一种特殊的reactive,它只会让对象的第一层属性响应式,而不会递归地让嵌套对象或数组响应式。

类比

想象你有一个文件夹,文件夹里有很多文件和子文件夹。shallowReactive只会关注文件夹里的文件,但不会关注子文件夹里的内容。

特点

  • 只追踪第一层属性:只有当你修改对象的第一层属性时,才会触发响应式更新。
  • 嵌套对象不响应式:如果你修改了嵌套对象的属性,不会触发响应式更新。

使用场景

  • 性能优化:当你有一个很大的对象,但只有第一层属性会频繁变化时,可以使用shallowReactive
  • 避免深层嵌套的响应式:如果你不希望嵌套对象也变成响应式,可以使用shallowReactive

示例

import { shallowReactive } from 'vue';

const state = shallowReactive({
  count: 1,
  nested: { value: 10 }
});

// 会触发更新
state.count = 2;

// 不会触发更新
state.nested.value = 20;

shallowRef和shallowReactive的区别

shallowRef

  • 适用对象:单个值(可以是对象、数组或基本类型)。
  • 触发更新:只有在完全替换value时才会触发更新。
  • 嵌套属性:不会追踪嵌套属性的变化。

shallowReactive

  • 适用对象:对象或数组。
  • 触发更新:只有在修改第一层属性时才会触发更新。
  • 嵌套属性:嵌套对象不会变成响应式。

示例对比

const myRef = shallowRef({ name: 'Paul', more: {} });
const myReactive = shallowReactive({ name: 'Paul', more: {} });

// myRef 不会解包 ref
myRef.value.name = 'Jack'; // 不触发更新
myRef.value = { name: 'Jack' }; // 触发更新

// myReactive 会解包第一层
myReactive.name = 'Jack'; // 触发更新
myReactive.more.name = 'Jack'; // 不触发更新

总结

  • shallowRef:只在引用变化时触发更新,不追踪嵌套属性的变化。
  • shallowReactive:只在第一层属性变化时触发更新,嵌套对象不会变成响应式。它们主要用于性能优化,当你不需要让整个对象或嵌套对象都变成响应式时,可以使用它们。

readonly和shallowReadonly

在 Vue 3 中,readonlyshallowReadonly是用于创建只读数据的 API,它们可以帮助开发者保护数据不被意外修改。

readonly(深度只读)

通俗理解

readonly会将一个对象的所有属性(包括嵌套属性)都变成只读的。这意味着你不能修改任何一层的属性值,否则会抛出错误。

类比

想象你有一个多层的保险箱,每一层都有锁。readonly就是把每一层的锁都锁上,确保里面的任何东西都不能被修改。

特点

  • 深度只读:递归地将对象的所有嵌套属性都变成只读的。
  • 不可修改:尝试修改任何属性都会被阻止,并在开发模式下抛出警告。
  • 适用于保护整个数据结构:当你希望保护整个对象不被修改时,使用readonly

示例

import { reactive, readonly } from 'vue';

const originalData = reactive({
  name: 'Alice',
  details: {
    age: 25,
    address: '123 Main St'
  }
});

const readOnlyData = readonly(originalData);

// 尝试修改第一层属性
readOnlyData.name = 'Bob'; // 报错:Cannot assign to 'name' because it is a read-only property.

// 尝试修改嵌套属性
readOnlyData.details.age = 30; // 报错:Cannot assign to 'age' because it is a read-only property.

shallowReadonly(浅层只读)

通俗理解

shallowReadonly只会将对象的第一层属性变成只读的,而不会影响嵌套对象的内部属性。这意味着你可以修改嵌套对象的属性,但不能修改第一层的属性。

类比

想象你有一个多层的保险箱,只有最外层的锁被锁上了,而里面的层没有锁。你可以修改里面的任何东西,但不能直接替换整个外层保险箱。

特点

  • 浅层只读:只将对象的第一层属性变成只读的,嵌套对象的属性可以被修改。
  • 适用于保护顶层结构:当你希望保护对象的顶层结构不被替换,但允许修改嵌套数据时,使用shallowReadonly

示例

import { reactive, shallowReadonly } from 'vue';

const originalData = reactive({
  name: 'Alice',
  details: {
    age: 25,
    address: '123 Main St'
  }
});

const shallowReadOnlyData = shallowReadonly(originalData);

// 尝试修改第一层属性
shallowReadOnlyData.name = 'Bob'; // 报错:Cannot assign to 'name' because it is a read-only property.

// 修改嵌套属性(不会报错)
shallowReadOnlyData.details.age = 30; // 可以正常修改
shallowReadOnlyData.details.address = '456 Elm St'; // 可以正常修改

readonly和shallowReadonly的区别

特性readonlyshallowReadonly
作用范围深度只读(递归)浅层只读(只限制第一层)
是否影响嵌套属性嵌套属性也变成只读嵌套属性可以被修改
适用场景需要保护整个数据结构不被修改需要保护顶层结构,但允许修改嵌套数据
性能更重(递归处理)更轻(只处理第一层)

使用场景

readonly的使用场景

  • 当你希望保护整个数据结构不被修改时,例如从外部获取的数据需要保持不变。
  • 当你需要确保数据的不可变性时,例如在某些状态管理场景中。shallowReadonly的使用场景
  • 当你只需要保护对象的顶层结构,但允许修改嵌套数据时。
  • 当你需要优化性能,避免递归处理整个对象时。

总结

  • readonly:深度只读,递归地将所有属性变成只读,适用于保护整个数据结构。
  • shallowReadonly:浅层只读,只将第一层属性变成只读,适用于保护顶层结构但允许修改嵌套数据。

选择哪一个取决于你的具体需求:如果你需要保护整个数据结构,就用readonly;如果你只需要保护顶层结构,就用shallowReadonly

toRaw和markRaw使用

在 Vue 3 中,toRawmarkRaw是两个用于处理响应式数据的工具函数,它们可以帮助开发者更精细地控制数据的响应式行为。

toRaw

通俗理解

toRaw是一个函数,用于获取一个响应式对象的原始(非响应式)对象。它可以帮助你绕过 Vue 的响应式系统,直接操作原始数据。

类比

想象你有一个带魔法的盒子(响应式对象),toRaw就是把魔法去掉,让你看到盒子里的真实东西(原始对象)。

特点

  • 返回原始对象:toRaw返回的对象是非响应式的,修改它不会触发 Vue 的响应式更新。
  • 临时操作:通常用于临时获取原始数据,而不影响响应式系统。

使用场景

直接操作原始数据: 当你需要直接修改数据而不触发响应式更新时,例如在性能敏感的代码中。

import { reactive, toRaw } from 'vue';

const state = reactive({ count: 0 });
const rawState = toRaw(state);

rawState.count = 10; // 修改原始对象,不会触发响应式更新
console.log(state.count); // 输出仍然是 0

与第三方库交互: 当你需要将数据传递给不支持 Vue 响应式系统的第三方库时。

const state = reactive({ data: [1, 2, 3] });
const rawState = toRaw(state);

thirdPartyLibrary(rawState.data); // 传递原始数据

markRaw

通俗理解

markRaw是一个函数,用于标记一个对象,使其永远不会被转换为响应式对象。即使将这个对象传递给reactiveref,它也不会变成响应式的。

类比

想象你有一个普通盒子(非响应式对象),markRaw就是给盒子贴上一个标签,告诉 Vue:“这个盒子不需要魔法!”即使 Vue 想把它变成魔法盒子,也不会成功。

特点

  • 保持非响应式:标记后的对象永远不会变成响应式对象。
  • 不可逆:一旦标记为markRaw,对象将永远保持非响应式。

使用场景

不需要响应式的数据结构: 当你明确不希望某个对象被转换为响应式对象时,例如工具类或静态配置。

import { reactive, markRaw } from 'vue';

class Helper {
  format(value) {
    return `Formatted: ${value}`;
  }
}

const helper = markRaw(new Helper());

const state = reactive({
  helper,
  data: 'test'
});

console.log(state.helper.format(state.data)); // 不会触发响应式更新

避免不必要的响应式转换: 当你需要将一个对象传递给 Vue 的响应式系统,但希望保持它的非响应式状态时。

const constants = markRaw({
  API_ENDPOINT: '/api/data',
  APP_NAME: 'MyApp'
});

const state = reactive({
  config: constants
});

console.log(state.config.API_ENDPOINT); // 不会变成响应式

toRaw和markRaw的区别

特性toRawmarkRaw
作用获取响应式对象的原始对象标记一个对象,使其永远不会变成响应式
返回值原始对象被标记的对象
响应式行为返回的对象是非响应式的被标记的对象不会变成响应式
使用场景需要直接操作原始数据需要保持对象的非响应式状态

总结

toRaw

  • 用于获取响应式对象的原始对象。
  • 适用于需要直接操作原始数据或与第三方库交互的场景。

markRaw

  • 用于标记一个对象,使其永远不会变成响应式对象。
  • 适用于需要保持对象非响应式状态的场景,例如工具类、静态配置或大型不可变对象。

通过合理使用toRawmarkRaw,可以更灵活地控制 Vue 的响应式系统,优化性能并避免不必要的响应式转换。