Vue组件响应式对象警告:markRaw/shallowRef为何能解决?
问题场景
我用defineComponent编写了一个带模板的组件,代码如下:
<script> import MySvgComponent from './MySvgComponent.vue' const MyComponent = defineComponent({ components: { MySvgComponent }, template: ` <div><MySvgComponent /></div> `, }) </script>
运行时弹出警告:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with
markRawor usingshallowRefinstead ofref
用markRaw包裹导入的组件即可解决警告,代码如下:
<script> import { markRaw } from 'vue' import MySvgComponent from './MySvgComponent.vue' const MyRawSvgComponent = markRaw(MySvgComponent); const MyComponent = defineComponent({ components: { MyRawSvgComponent }, template: ` <div><MyRawSvgComponent /></div> `, }) </script>
想搞清楚三个问题:
- 为什么用
markRaw能解决这个警告? markRaw返回对象的非代理版本,为什么这里需要这么做?markRaw或shallowRef解决的具体性能问题是什么?
问题解析
1. 为什么markRaw能解决警告?
Vue的响应式系统会自动把普通对象转为Proxy代理。如果组件对象(本质是包含配置的普通对象)意外被响应式系统处理了——比如嵌套在reactive对象里,或是被第三方工具/逻辑触发了响应式转换——Vue检测到这个组件是代理对象时,就会抛出警告。
markRaw的核心作用就是标记一个对象,让它永远不会被Vue的响应式系统转为Proxy。用它包裹组件后,组件对象保持原始状态,Vue检测到它不是代理对象,自然就不会触发警告。
2. 为什么需要组件对象的非代理版本?
组件本身是静态配置对象,它完全不需要响应式能力。Vue渲染组件时,只需要读取配置里的template、props、methods等内容来创建实例,根本不需要追踪它的变化——毕竟组件配置在运行时不应该被动态修改,也没必要修改。
如果组件被转为响应式代理,响应式系统会给它套上一层依赖追踪的逻辑,但这些逻辑全是多余的,对组件的正常运行没有任何帮助。
3. 解决的具体性能问题是什么?
- 无意义的Proxy创建开销:把组件转为Proxy需要消耗CPU和内存资源,当项目里有大量组件时,这种冗余转换会累积成明显的性能负担。
- 冗余的依赖追踪检查:响应式对象每次被访问时,Vue都会做依赖收集的检查。组件配置在渲染过程中会被多次读取,这些额外的检查会增加运行时的性能消耗。
- 潜在的意外行为:虽然组件配置一般不会被修改,但如果不小心修改了代理后的组件对象,可能会触发不必要的响应式更新,导致组件异常渲染。
用markRaw或shallowRef(shallowRef只会对值的最外层做响应式,内部对象不会被转为Proxy),能让组件对象保持原始状态,彻底避免这些无意义的性能开销和潜在问题。
内容的提问来源于stack exchange,提问作者Harrison Cramer

