shallowReadonly 的核心作用是锁住对象第一层属性的赋值操作,同时放行所有深层修改,适用于组件通信、状态分发等场景的按需权限控制。

shallowReadonly 的核心作用就是锁住对象第一层属性的赋值操作,同时放行所有深层修改——它不是“全盘冻结”,而是“表层设防”。这种特性天然适合做按需权限控制,尤其在组件通信、状态分发、配置透传等场景中,能精准隔离“谁可以改结构”和“谁可以改内容”。
理解 shallowReadonly 的拦截边界
它只拦截三类顶层操作:
- 直接赋值:如 obj.user = { ... } → 报错
- 删除属性:如 delete obj.list → 报错
- 重定义属性:如 Object.defineProperty(obj, 'config', {...}) → 报错
但以下操作完全不受影响:
- obj.profile.name = 'Tom'(深层对象属性可写)
- obj.items.push({id: 1})(嵌套数组方法可用)
- obj.meta.updateTime = Date.now()(任意嵌套路径赋值)
在父子组件间实现读写权限分离
父组件拥有完整控制权,子组件只能更新内部状态,不能替换整个数据块:
- 父组件用 reactive 管理完整状态,再用 shallowReadonly 包装后传给子组件
- 子组件接收后可调用 props.formModel.name = 'new' 或 props.tableData.rows[0].status = 'done'
- 但一旦写 props.formModel = {...} 或 props.tableData = null 就会触发警告并静默失败
这样既避免了子组件意外覆盖配置结构,又保留了字段级响应式更新能力。
配合 computed 实现动态权限裁剪
当不同角色需要看到/编辑不同字段时,可结合 computed + shallowReadonly 构建视图专用副本:
- 定义原始响应式数据 const rawState = reactive({...})
- 根据用户角色计算出应暴露的顶层字段名数组,如 ['user', 'permissions']
- 用 computed(() => Object.fromEntries(Object.entries(rawState).filter(...))) 提取子集
- 再对结果调用 shallowReadonly,确保子集结构不可被替换
这种方式比手动维护多个 readonly 对象更灵活,也比全量 shallowReadonly 更细粒度。
规避常见误用陷阱
几个高频踩坑点必须注意:
- 不要对 ref.value 直接 shallowReadonly:应写成 shallowReadonly(myRef.value),而非 shallowReadonly(myRef)
- 解构会丢失代理:写 const { user } = shallowReadonlyObj 后,user 是普通对象,修改它不会受保护,也不触发响应式更新
- 不适用于非响应式原始对象的“深度防护”:shallowReadonly 只加 Proxy 层,不递归 reactive;若需深层响应式,仍要搭配 reactive 或 ref
本质上,shallowReadonly 是一个轻量、可控、有明确边界的访问控制工具,用对了,就能在不牺牲灵活性的前提下守住关键结构安全。

















