toRaw 与 markRaw
toRaw()
概念:toRaw()
可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象
首先,我们都知道, reactive()、 shallowReactive() 、readonly()或者 shallowReadonly() 这四个方法都是用来创建响应式对象,只不过是深层响应还是浅层响应的问题。
而 toRaw() 则是接收上面这四个方法定义的响应式对象,然后获取未经处理的原始对象。
let person = {name: "al",age: 28,job: {j1: {work: "前端",salary: 2,},},arr: [1, 2, 3],
};let reactivePer = reactive(person);
console.log(reactivePer); // Proxy(Object) 深度响应对象
console.log(toRaw(reactivePer)); // 源对象 {name: 'al', age: 28, job: {…}, arr: Array(3)}let shallowReactivePer = shallowReactive(person);
console.log(shallowReactivePer); // Proxy(Object) 浅层响应对象
console.log(toRaw(shallowReactivePer));// 源对象 {name: 'al', age: 28, job: {…}, arr: Array(3)}
那么问题就来了,我的 ref() 函数 ,在定义对象类型的响应式数据时,其底层也是通过 reactive() 函数来实现响应式转化的。那如果我通过 ref() 定义的对象能否被 toRaw() 转化为原始对象么?
let person = {name: "al",age: 28,job: {j1: {work: "前端",salary: 2,},},arr: [1, 2, 3],
};let refPer = ref(person);// RefImpl 响应式对象
console.log(refPer); // 经过ref转化的数据,在js中使用时需要.value ,
//返回原始对象 {name: 'al', age: 28, job: {…}, arr: Array(3)}
console.log(toRaw(refPer.value));
测试可知,通过 ref() 定义的对象类型响应式数据,toRaw()函数也能返回原始对象
那么 toRaw() 函数能否 将 ref() 函数定义的基本类型数据返回原始对象么?
答案是不会,ref() 函数定义的基本类型的值,通过 toRaw() 转化之后,也只会得到初始值,而不是对象
let refSum = ref(0)// RefImpl 响应式对象
console.log(refSum);// refSum.value 值就是一个基础类型数据,所以返回的是原始值 0,并不是一个对象
console.log(toRaw(refSum.value));
但是这里需要注意一个问题,那就是 toRaw() 的单层处理:toRaw()只能作用于传入的那个对象本身,并将该对象还原为其未代理的原始对象。如果这个对象内部有嵌套的响应式对象,toRaw
不会自动递归处理这些嵌套的对象,也就是说它只会处理调用它时直接传入的那一层对象,而不会处理更深层次的嵌套对象。
还是之前的例子:
let person = {name: "al",age: 28,job: {j1: {work: "前端",salary: 2,},},arr: [1, 2, 3],
};let reactivePer = reactive(person);
console.log(reactivePer); // Proxy(Object) 深度响应对象
console.log(toRaw(reactivePer)); // 源对象 {name: 'al', age: 28, job: {…}, arr: Array(3)}
在这个例子中,reactivePer
是一个深度嵌套的响应式对象。reactivePer
本身是响应式的,同时 job、j1、arr也是被代理的响应式对象。
当你调用 toRaw
时:toRaw() 将 reactivePer 还原成了person,即最初传入的那个非响应式对象
console.log(toRaw(reactivePer) === person); // true
但 toRaw
不会自动处理 reactivePer.job、
reactivePer.job.j1或
reactivePer.arr
,它们仍然是响应式的,除非你单独对它们调用 toRaw
。
如果你只对某一层的嵌套对象调用 toRaw
,例如:
const job = toRaw(reactivePer.job);console.log(job=== person.job); // true
- 这里,
toRaw(reactivePer.job)
将 job还原为reactivePer.job
,即未代理的原始对象。 - 但
reactivePer.job.j1
仍然是一个响应式对象,除非你也对它调用toRaw
。
如果你希望获取整个嵌套对象的原始版本,你需要对每一层嵌套对象分别调用 toRaw
。
使用场景:
- 性能优化:在某些情况下,你可能不希望某些操作触发响应式更新,尤其是在处理大数据结构或频繁修改数据时。使用
toRaw
可以绕过响应式代理,直接操作原始数据,避免不必要的性能开销。const reactiveArray = reactive([1, 2, 3]);// 使用 toRaw 操作原始数组,不触发响应式更新 toRaw(reactiveArray).push(4);
- 与第三方库集成:当你将 Vue 的响应式对象传递给不兼容响应式代理的第三方库时,可能会导致意外行为或错误。在这种情况下,可以使用
toRaw
将对象转换为原始对象后再传递给第三方库。import { reactive, toRaw } from 'vue'; import thirdPartyLib from 'some-third-party-lib';const reactiveData = reactive({ name: 'Alice' });// 将原始数据传递给第三方库 thirdPartyLib.processData(toRaw(reactiveData));
-
避免循环引用:在复杂的响应式对象结构中,可能会发生循环引用。通过使用
toRaw
,可以避免在序列化或深度克隆对象时发生循环引用错误。import { reactive, toRaw } from 'vue';const obj = {}; obj.self = obj;const reactiveObj = reactive(obj);// 序列化前将对象转换为原始对象,避免循环引用问题 const jsonString = JSON.stringify(toRaw(reactiveObj));
-
对比原始数据:有时候你可能需要将一个响应式对象与其原始对象进行对比,来检查数据是否发生了变化。在这种情况下,
toRaw
可以帮助你提取原始对象进行对比import { reactive, toRaw } from 'vue';const originalObj = { count: 0 }; const reactiveObj = reactive(originalObj);// 检查响应式对象是否与原始对象相同 if (toRaw(reactiveObj) === originalObj) {console.log('Objects are the same'); }
-
克隆响应式对象:当你需要克隆一个响应式对象时,可以使用
toRaw
提取原始对象,然后进行深度克隆,以避免响应式特性被克隆。import { reactive, toRaw } from 'vue'; import cloneDeep from 'lodash/cloneDeep';const reactiveObj = reactive({ count: 0 });// 使用 toRaw 提取原始对象,然后进行深度克隆 const clonedObj = cloneDeep(toRaw(reactiveObj));
markRaw()
概念:将一个对象标记为不可被转为代理。返回该对象本身。
用于标记某个对象为“原始对象”(非响应式对象),从而确保该对象不会被 Vue 的响应式系统代理。这意味着当你使用 markRaw
处理一个对象后,即使将它传递给 reactive
或 ref
,该对象仍然保持原样,不会被转换为响应式对象。
let person = {name: "al",age: 28,job: {j1: {work: "前端",salary: 2,},},arr: [1, 2, 3],
};let markRawPer = markRaw(person);let reactivePer = reactive(markRawPer);
console.log(reactivePer === person); // true,reactive定义的数据与原始数据完全相同let refPer = reactive(markRawPer);
console.log(refPer === person); // true,ref定义的数据与原始数据完全相同
使用场景:
- 第三方库的数据对象:当你使用第三方库时,这些库的数据结构通常不需要响应式处理。如果你希望这些数据对象在 Vue 组件中使用时不触发响应式系统,可以使用
markRaw
。import { markRaw, reactive } from 'vue';// 假设这是来自第三方库的数据对象 const thirdPartyData = markRaw({ name: 'Alice', age: 30 });// 即使将它包裹在 reactive 中,它也不会变成响应式对象 const state = reactive({user: thirdPartyData });// 更改 thirdPartyData 的属性不会触发 Vue 响应式系统 state.user.age = 31; // 不会触发响应式更新
-
性能优化:在某些情况下,你可能有大量的静态数据不需要响应式处理。通过使用
markRaw
,可以避免 Vue 进行不必要的代理,从而提升性能。类似于 Vue2中的冻结数据import { markRaw, reactive } from 'vue';const largeStaticData = markRaw({items: [/* 大量数据 */] });const state = reactive({data: largeStaticData });// `largeStaticData` 不会触发响应式系统,减少性能开销
-
避免循环引用:案例同上
总结
1、toRaw:将一个响应式对象转为原始对象,ref定义的基础类型数据不会被转换
2、toRaw:单层处理,toRaw()只能作用于传入的那个对象本身,并将该对象还原为其未代理的原始对象。如果你希望获取整个嵌套对象的原始版本,你需要对每一层嵌套对象分别调用 toRaw
。
3、toRaw使用场景:包括但不限于性能优化、第三方库集成、避免循环引用、对比原始数据、克隆响应式对象等
4、markRaw
:标记某个对象为“原始对象”(非响应式对象),从而确保该对象不会被 Vue 的响应式系统代理。
5、markRaw
使用场景:包括但不限于性能优化、第三方库集成、避免循环引用等