通过使用
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>
创建一个对象的深只读副本,对于 ref
和 reactive
都生效
特点:
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>
作用:与 readonly
类似,但只作用于对象的顶层属性
用法:
jsconst original = reactive({ ... });
const shallowReadOnlyCopy = shallowReadonly(original);
特点:
用于获取一个响应式对象( reactive()
、readonly()
、shallowReactive()
或者 shallowReadonly()
)的原始对象,toRaw
返回的对象不再是响应式的,不会触发视图更新
这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
在需要将响应式对象传递给非 Vue 的库或外部系统时,使用 toRaw
可以确保它们收到的是普通对象
typescriptimport { reactive, toRaw } from 'vue';
let person = reactive({
name: 'Morales',
age: 23
});
console.log('person', person); // 响应式对象
console.log('toRaw(person)', toRaw(person)); // 原始对象
标记一个对象,使其永远不会变成响应式的
例如,使用mockjs
时,为了防止误把mockjs
变为响应式对象,可以使用 markRaw
去标记mockjs
typescriptlet car = markRaw({
brand: 'BMW M3 GTR',
price: 150
});
let car2 = reactive(car);
console.log('car', car)
console.log('car2', car2)
谨慎使用
markRaw()
和类似 shallowReactive()
这样的浅层式 API 使你可以有选择地避开默认的深度响应/只读转换,并在状态关系谱中嵌入原始的、非代理的对象。它们可能出于各种各样的原因被使用:
这应该是一种进阶需求,因为只在根层能访问到原始值,所以如果把一个嵌套的、没有标记的原始对象设置成一个响应式对象,然后再次访问它,你获取到的是代理的版本。这可能会导致对象身份风险,即执行一个依赖于对象身份的操作,但却同时使用了同一对象的原始版本和代理版本:
typescriptconst foo = markRaw({
nested: {}
})
const bar = reactive({
// 尽管 `foo` 被标记为了原始对象,但 foo.nested 却没有
nested: foo.nested
})
console.log(foo.nested === bar.nested) // false
识别风险一般是很罕见的。然而,要正确使用这些 API,同时安全地避免这样的风险,需要你对响应性系统的工作方式有充分的了解。
创建一个自定义的 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
typescriptimport { 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>
本文作者:Morales
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!