skip to content
usubeni fantasy logo Usubeni Fantasy

Vue 组件的 Props 到底能不能改?

/ 5 min read

单向数据流

Vue 推崇单向数据流这个概念,也就是数据流向必须是从父到子。子组件想要修改数据必须 emit 一个事件,父组件接收到事件后,由父组件修改数据传回到子组件。

我在 stackblitz 写了个例子,使用版本为 Vue@3.4.5。可以看到在,props proxy 的外层 handler 是 ReadonlyReactiveHandler

于是当你想直接修改 props 的时候会被狠狠拦住,提示 VM383:1 [Vue warn] Set operation on key "msg" failed: target is readonly.

留个后门

也是上面的图,能看到在 ReadonlyReactiveHandler 底下的目标数据仍然是原来的响应式数据(就是同一个,===true),所以底层的数据仍然是可以改的,并且是符合正常响应式表现的

官方文档本身就有提到这种直接修改 props 里的对象或数组的行为,态度是不推荐,因为这么做会导致数据流混乱,造成数据不知道在哪被改了的情况。但是文档也有提到,如果父子组件本身就是设计得紧密耦合的话,直接改也不是不行。

虽然 readonly() 本来就可以把整个响应式对象设置为只读,不这么做的原因据文档说是考虑到会有性能问题,而且确实会有少数这样的需求。于是子组件直接修改 props 的后门就留下来了。

P.S. 回想起来好像以前也跟 Vuex 那种数据更新哲学有关,总之大家都习惯了单向数据流的设定。

直接改还是香

我常常遇到直接把一个对象传到 v-model 的情况,子组件会对 modelValue 对象做一些修改,例如其中一个字段修改会重置另一个字段。因为上面提到的,props 底层可以直接改,那么就是直接就改好了,再加个 emit 是不是有点多此一举。

即使是非对象值得情况,好像 Vue 本身就在纵容这个行为。v-modeldefineModel() 都在简化单向数据流麻烦的操作,也是只需要一个等号就把数据更新到父组件了。确实,在大多数情况我都根本不在乎他是不是单向数据流,此时此刻只想修改数据,仅此而已。

computed? readonly!

其实早在 Vue2 就在用“直接改 props”这个“feature”,但是为什么直到现在才水这么一篇呢?直接原因其实是在工作中突然遇到了“明明是 props 的底层对象,却总是提示 readonly 的情况”。

一开始以为是可写 computed 导致内层只读,但测试了一下发现不是,computed 只是一个快照,里面的东西如果本来是响应式的,那么他就是响应式的,如果本来是普通对象,那便是普通对象,跟 props 一样 computed 也是只有最外层只读。

那如果 computed 本身不会让数据里层变成只读,那就只能说这个数据从来源上就已经是只读了……事实也是如此,那是从父组件的父组件传来的一个 useRequest 的数据,本身就不可改。

Vue 提供了 readonly(),可以把响应式数据限制为只读(并且深层也只读),不太常用,但在写 use 函数的时候可以用它提醒用户不能修改这个响应式数据,这个数据完全由 use 接管,所以写库的时候挺实用的。

最后回答标题,结论就是,只有 props 本身只读,内部对象保持原样。如果本来就是响应式数据,那么响应式特性依然存在,改了会更新页面……但文档上还是不推荐直接改。

相关链接

评论组件加载中……