Vue不提供强制不可变API,但可通过readonly实现语义不可变、shallowRef/shallowReactive减少响应式开销、markRaw跳过响应式转换、computed+toRefs构建只读派生数据。

Vue 响应式数据本身默认是可变的(ref 可赋新值,reactive 对象属性可修改),但“不可变数据”不是 Vue 的原生目标,而是开发中为规避副作用、提升可预测性或配合函数式编程风格所采用的设计策略。Vue 并不提供“强制转为不可变”的 API,但可通过组合现有机制 + 明确约束,实现语义上等效的不可变行为,并带来性能收益。
用 readonly 包裹响应式数据,阻止运行时修改
readonly 是 Vue 3 提供的只读代理工具,它不改变原始数据,而是返回一个无法被 setter 修改的响应式代理。这是最直接、最安全的“不可变化”手段:
- 对
reactive对象:用readonly(obj)后,任何属性赋值(obj.count = 2)在开发模式下会抛出警告,生产模式下静默失败,且不会触发更新 - 对
ref:用readonly(myRef)后,myRef.value = ...同样失效 - 支持深层只读:嵌套对象、数组均被递归保护,比手写
Object.freeze更可靠(后者不拦截新增属性,且对 Proxy 无效)
用 shallowRef / shallowReactive 避免深层响应式开销
当数据结构庞大但仅需顶层变化响应时(如大型配置对象、API 返回的原始 JSON),深层响应式不仅无意义,还会拖慢初始化和更新速度。此时“不可变”不是目的,但效果类似:
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
-
shallowRef:只让.value本身响应,内部值(哪怕是个复杂对象)不转为 reactive,修改其属性不会触发更新 -
shallowReactive:只代理对象第一层属性,嵌套对象保持原样,访问obj.nested.count不会自动解包或追踪 - 两者都显著减少依赖收集数量和 Proxy 拦截层级,特别适合静态数据或仅用于渲染的只读场景
用 markRaw 标记外部不可变实例,跳过响应式转换
第三方库实例(如 Chart 实例、Three.js 场景、Date 对象、正则表达式)通常自身已具备状态管理或不可变契约。若误交由 reactive 处理,不仅浪费性能,还可能破坏其内部逻辑:
立即学习“前端免费学习笔记(深入)”;
-
markRaw()告诉 Vue:“这个值永远不要转成响应式”,它会被原样保留,不经过 Proxy 或 RefImpl 封装 - 常用于:
const chart = markRaw(new ECharts(...));const now = markRaw(new Date()) - 搭配
readonly使用更严谨:例如readonly(markRaw(...)),既跳过响应式,又防止意外修改
结合 computed 与 toRefs 实现派生数据的逻辑不可变
真正的不可变思维不在于锁死数据,而在于切断可变源头。通过 computed 衍生只读计算属性,并用 toRefs 解构后暴露,能天然形成单向数据流:
const state = reactive({ a: 1, b: 2 }); const derived = computed(() => ({ sum: state.a + state.b })); const { sum } = toRefs(derived);- 解构出的
sum是只读 ref,无法赋值;且其值完全由state决定,源头可控 - 避免在模板中重复计算(如
{{ a + b }}),也避免将原始state直接暴露给子组件造成意外修改


















