props本身是只读响应式代理,不应在子组件中冻结;真正需冻结的是父组件的数据源,如用Object.freeze()提前冻结静态配置再传递,既保障不可变性又不破坏响应式。

Vue 组件中 props 本身是只读的响应式代理,但“深度冻结 props”并不是一个标准操作——因为 props 不该被冻结,也不应被修改;真正需要控制的是 传入 props 的原始数据源。关键在于:冻结发生在父组件的数据准备阶段,而非子组件接收后。只要理解这个边界,就能兼顾不可变性与响应式更新。
props 不能也不该被冻结
Vue 内部对 props 做了只读代理(readonly(reactive(rawProps))),你无法、也不应尝试在子组件中调用 Object.freeze(props) 或类似操作:
- 它不会增强安全性——props 本就禁止赋值,强行冻结会抛出警告或静默失败
- 它会破坏响应式追踪——冻结后内部 Proxy 失效,子组件将无法响应父组件后续变更
- 它违背单向数据流设计意图,属于对 Vue 运行时机制的误用
真正该冻结的是父组件的数据源
如果你希望某段配置、枚举或静态列表不被意外修改,且又需作为 prop 传递,应在 父组件定义阶段 就冻结原始数据:
- 使用
Object.freeze({ title: '报表', type: 'chart' })创建不可变对象,再通过:config="frozenConfig"传入 - 对大型静态数组(如菜单项、国家列表)提前冻结,避免深层响应式开销
- 配合
ref或reactive使用:例如const staticOptions = ref(Object.freeze([...])),既保持 ref 容器可响应,又锁定其 .value 内容
冻结后仍能保持响应式更新的条件
冻结不影响响应式,前提是:
立即学习“前端免费学习笔记(深入)”;
- 冻结的是值本身(如对象、数组),而不是 ref 或 reactive 包装器
- 父组件仍通过响应式引用传递(如
:options="staticOptions"),Vue 能追踪 ref.value 的替换 - 子组件监听的是整个 prop(如
watch(() => props.options, ...)),而非试图修改冻结对象内部属性
替代冻结的轻量级方案
多数场景下,无需显式冻结,可用更自然的方式达成目的:
- 用
const声明字面量对象/数组,配合 TypeScript 类型约束防止误改 - 在 props 定义中加
validator校验结构,比运行时冻结更早拦截非法输入 - 对只读配置使用
computed(() => ({ ... }))返回新对象,天然不可变且响应依赖变化


















