hmr 与 vue 响应式系统分属不同层级但协同工作:hmr 负责替换代码,响应式系统保障状态保留与更新触发;vue 通过复用响应式对象、依赖关系和 watcher 实现无感热更新,但结构变更或引用链断裂会导致失效。

HMR 和 Vue 响应式系统不是同一层机制,但它们在开发过程中紧密协作——HMR 负责“换代码”,响应式系统负责“保状态、触更新”。两者配合,才能实现在不刷新页面的前提下,既替换组件逻辑,又保留输入、计数、滚动位置等运行时状态。
Vue 的响应式系统是 HMR 状态保留的基础
Vue 组件的 data、ref、computed、watch 等状态都依托于响应式系统(Vue 2 用 Object.defineProperty,Vue 3 用 Proxy)进行追踪。当 HMR 触发组件更新时,新模块加载后,Vue 不会销毁旧实例,而是将新的 template、script 逻辑“注入”到原有响应式对象上:
- data 中的属性仍由同一套 getter/setter 管理,原有依赖关系未断裂
- setup() 返回的响应式对象(如
reactive或ref)被复用,内部__v_isRef或__v_reactive标识保持有效 - watcher 实例未被销毁,仍监听相同响应式源,变更后继续触发视图更新
HMR 如何利用响应式系统实现“无感更新”
以一个 Counter.vue 为例:修改 count 的初始值或点击逻辑后,HMR 并不重建组件实例,而是通过以下步骤协同响应式系统完成热更新:
- 构建工具(Vite/Webpack)检测文件变更,生成新模块代码并推送到浏览器
-
@vitejs/plugin-vue或vue-loader解析新 SFC,提取 script setup、template 编译结果 - HMR runtime 调用
module.hot.accept,触发 Vue 的 HMR 插件逻辑 - Vue 将新组件选项(或 setup 函数)与旧实例合并:保留原有响应式数据对象、生命周期状态、DOM 引用,仅替换 render 函数和事件处理器
- 响应式系统感知到新 render 函数中访问了相同的 ref/data,自动触发一次 reactivity-driven 的重新求值与 patch
哪些情况会导致响应式 + HMR 协作失效?
并非所有修改都能保留状态,关键在于是否破坏了响应式对象的引用链或触发强制卸载:
- 修改顶层
export default结构(如删掉setup()、改写成data()形式),导致实例重建 - 在
setup()中新增/删除ref或reactive声明,新模块无法映射旧响应式变量 - 使用
key强制重渲染(如:key="Math.random()"),绕过 HMR 的实例复用逻辑 - 副作用清理异常(如未正确 return cleanup 函数),旧 watch/watchEffect 未被清除,新逻辑重复注册
- 全局状态(Pinia store、provide/inject)未做 HMR 适配,其内部响应式对象未被复用
开发者可主动增强 HMR 与响应式的协同效果
不需要手动写 module.hot.accept,但可通过以下方式提升稳定性:
- 优先使用
defineComponent显式声明组件,避免匿名函数导致 HMR 无法识别模块边界 - 对复杂逻辑抽离为独立 composable,并在
setup()中统一调用,便于 HMR 定位更新粒度 - Pinia store 配合
acceptHMRUpdate钩子,在热更新时重置 store state 而非重建整个 store 实例 - 避免在
mounted中直接操作 DOM 或绑定第三方库实例(如 Chart.js),改用onMounted+onBeforeUnmount清理
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











