内容提要
Vue 推崇单向数据流,props 外层只读,直接修改会警告,但其底层对象仍是原响应式数据,可直接修改并触发更新,官方不推荐,仅在父子紧密耦合时可接受。v-model、defineModel 简化了数据更新。computed 也只外层只读,内层只读与否取决于数据来源,readonly() 可深层限制。
延伸解读
Props 只读的边界在哪里
文章指出,Vue 的 props 代理外层是只读的,直接修改会触发警告,但底层目标数据仍是原来的响应式对象。这意味着如果 props 传递的是对象或数组,修改其内部属性不会触发警告,且能正常更新页面。这种“后门”是官方有意保留的,因为完全深层只读可能带来性能问题,且少数场景确有需求。
直接修改 props 的适用场景与风险
官方不推荐直接修改 props 内部数据,因为会导致数据流混乱,难以追踪数据在何处被修改。但文档也提到,如果父子组件本身设计为紧密耦合,直接修改是可接受的。文章作者在实践中常遇到将对象传给 v-model 后子组件直接修改字段的情况,认为再加 emit 显得多余。v-model 和 defineModel 也在简化这类操作。
computed 与 readonly 的只读特性
computed 和 props 一样,只有最外层是只读的,内部数据是否只读取决于其来源。如果来源是响应式数据,内部仍可修改;如果来源是普通对象,则内部也是普通对象。readonly() 则不同,它能将响应式数据限制为深层只读,适合在编写 use 函数或库时使用,以提醒用户数据由内部接管、不可修改。
Q&A
Vue 中直接修改 props 会有什么后果?
直接修改 props 外层会触发警告,提示 target is readonly,因为 props 外层是只读的。但 props 底层的对象或数组仍然是原来的响应式数据,直接修改它们可以生效并触发更新,不过官方不推荐这样做,因为会导致数据流混乱。
为什么 Vue 不推荐直接修改 props 里的对象或数组?
因为这样做会导致数据流混乱,造成数据不知道在哪被改了的情况。Vue 推崇单向数据流,子组件应该通过 emit 事件让父组件修改数据。但在父子组件紧密耦合的情况下,直接修改也是可以接受的。
Vue 的 props 底层数据为什么可以被修改?
因为 props 的只读代理(ReadonlyReactiveHandler)只作用于外层,底层的目标数据仍然是原来的响应式数据(同一个对象),所以直接修改底层数据会触发响应式更新。这是 Vue 故意留的后门,考虑到性能问题和少数需求。
v-model 和 defineModel 如何简化 props 的修改?
v-model 和 defineModel() 简化了单向数据流的操作,只需要一个等号就能把数据更新到父组件,避免了手动 emit 事件的麻烦。在大多数情况下,开发者可能并不关心单向数据流,只想直接修改数据。
computed 属性是只读的吗?它的内层数据可以修改吗?
computed 只有最外层是只读的,内层数据是否只读取决于数据来源。如果内层数据本身是响应式的,那么它就是响应式的,可以修改;如果本来是普通对象,那就是普通对象。computed 只是一个快照,不会让内层数据变成只读。
Vue 的 readonly() 有什么作用?什么时候使用?
readonly() 可以把响应式数据限制为只读,并且深层也只读。它不太常用,但在编写 use 函数时可以用它提醒用户不能修改这个响应式数据,因为数据完全由 use 接管,适合在写库时使用。