Vue 3 只读对象特定键值修改策略
2024-12-20 23:31:29
Vue 3 中只读对象的特定键值修改策略
在 Vue 3 开发中,readonly()
函数可将一个响应式对象转换为只读版本。这个只读版本禁止对属性进行直接修改。特定情况下,需要允许对只读对象中的部分键值进行修改,这带来了挑战。针对如何允许在Vue 3 readonly
对象中编辑某些键这一问题,提供一些实用的解决方案。
问题根源:readonly()
的作用机制
readonly()
通过 Proxy 代理目标对象实现只读功能。代理会拦截对对象的所有 setter 操作,从而阻止对任何键值修改。这种机制保护了对象状态不被意外修改,也确保了应用的可预测性。需要修改的键会被 readonly()
创建的 Proxy 捕获并阻止修改。
方案一:使用 markRaw()
标记需要修改的键值
对于无需响应性的键,可以使用 markRaw()
阻止 readonly()
对这些属性施加只读保护。被标记的属性保持了响应式但能被修改。
原理说明
markRaw()
函数会将对象标记为“原始”状态。这个状态告诉 Vue 不要将其转换为响应式对象,防止对某个子对象添加 Proxy 代理。将希望允许修改的键对应的子对象使用 markRaw()
标记,该子对象在传递给 readonly()
函数时会被保留下来,因此 readonly
将允许修改这个被标记为"原始"的属性值。
操作步骤
- 使用
markRaw()
标记需修改键值。 - 使用
readonly()
创建只读对象。
代码示例
import { reactive, readonly, markRaw } from 'vue'
const obj = reactive({
name: 'example',
target: markRaw({ value: 'DOM' })
})
const readonlyObj = readonly(obj)
// 可以正常修改
readonlyObj.target.value = 'Modal'
console.log(readonlyObj.target.value) // 输出: Modal
// 下面会触发警告: 'Set operation on key "name" failed: target is readonly.'
readonlyObj.name = 'test'
方案二:构建自定义的浅只读对象
基于需求创建一个允许修改指定键的自定义函数,这个函数需要基于 readonly
实现但排除指定键。可以封装此行为的函数实现类似readonly
的功能,但可以根据需要开放某个属性的修改权限。
原理说明
customReadonly
实现一个自定义版本的 readonly()
函数。它接受一个对象和一个键名数组作为参数,其中数组指定要开放修改的键。内部使用 toRaw()
可以将 readonlyObj
转换为普通的非只读对象, 就可以对其进行操作修改, 修改后需要再返回 readonly
对象即可。这个自定义函数使用 Proxy 创建了一个只读对象,通过 toRaw
方法获取被 readonly
包装对象的原始值从而可以修改 excludedKeys
指定的键,然后继续将其转换为 readonly
对象进行保护,实现自定义功能。
操作步骤
- 定义自定义函数,利用 Proxy 创建代理。
- 设置 setter,检查是否为允许修改的键。
- 对于允许的键,通过原始对象修改;否则,阻止修改。
代码示例
import { reactive, readonly, toRaw } from 'vue'
function customReadonly(obj, excludedKeys) {
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
return res;
},
set(target, key, value, receiver) {
if (excludedKeys.includes(key)) {
const rawTarget = toRaw(target); //将target转化为普通对象再进行修改, toRaw()方法无法对基础类型数据起效,必须为复杂对象数据类型。
rawTarget[key] = value;
//返回新的 readonly() 对象进行保护。
return true;
} else {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
return true;
}
}
});
}
const obj = reactive({
name: 'example',
target: 'DOM',
info: {
title: 'content'
}
})
const readonlyObj = customReadonly(obj, ['target', 'info']);
// 修改排除的键,正常执行。
readonlyObj.target = 'Modal'
readonlyObj.info.title = 'test'
// 会输出告警
readonlyObj.name = 'new name';
方案三:结合计算属性派生新对象
创建一个计算属性,该计算属性基于原只读对象生成一个新的对象。允许修改特定键,但不直接改变原对象,创建计算属性可以在一个独立的范围内去创建和修改部分属性, 同时维持 readonly
属性原本的属性不变,提供安全保障。
原理说明
使用 computed()
创建一个计算属性。这个计算属性返回一个新的对象,新对象从原始的只读对象派生而来。通过对象展开运算符浅拷贝只读对象的属性到一个新的对象中, 然后在这个新的对象中就可以根据需要, 单独允许修改需要的键。由于计算属性只在其依赖项发生更改时重新计算,且没有 setter
属性, 在需要响应性和不可变性保证的同时处理例外情况有明显作用。
操作步骤
- 定义计算属性。
- 使用展开运算符和键值覆盖创建新对象。
- 返回该新对象。
代码示例
import { reactive, readonly, computed, ref } from 'vue'
const obj = reactive({
name: 'example',
target: 'DOM'
})
const readonlyObj = readonly(obj)
const derivedObj = computed(() => ({
...readonlyObj,
// 通过修改此处的值,可以随时修改
target: 'Modal'
}))
console.log(derivedObj.value.target); // 输出: Modal
// 再次触发告警: 'Set operation on key "name" failed: target is readonly.'
readonlyObj.name = 'test'
安全建议
当尝试绕过 readonly
保护时,要注意应用状态的稳定。直接修改 readonly
对象可能导致意外副作用,尤其是在多人协作或复杂状态管理的项目中。遵循 Vue 3 的设计理念,尽量避免破坏 readonly
的完整性,使用上述方法来确保修改符合预期。进行一些键值上的修改时候, 应当保证数据类型的统一,对于类型改变、空值等异常状态需要做单独的处理以防影响原本对象的类型和结构, 导致应用运行期间报错,通过合理的使用和实践, 可以更好的控制项目的开发状态, 同时对异常情况处理也能保证安全性。