Webpack 的 HMR 热更新是什么?为什么有时保留状态,有时整页刷新?
下面是一段教学用的模拟面试。
🧑💻 面试官: HMR 是什么?和自动刷新页面有什么区别?
🙋♂️ 我: 改完代码后,页面会自动更新,不用手动刷新。
🧑💻 面试官: 自动整页刷新也不用手动操作。HMR 为什么有时保留输入,有时又刷新?
🙋♂️ 我: 它应该只替换改动的模块。
🧑💻 面试官: 那旧模块的定时器怎么办?改了三次代码,为什么日志开始打印三份?
HMR 不只是“把新文件送过来”。还要有人接住更新,并处理旧模块留下的资源。
面试速答(60 秒版)
HMR,也就是模块热替换,是在开发过程中更新部分模块,而不必每次都重新加载整个页面。
以 Webpack 为例,代码变化后生成更新信息,开发服务通知浏览器,浏览器中的 HMR 运行时找到受影响的模块,再沿依赖关系寻找能接受更新的边界。
如果更新能被接受,就替换模块并执行相应的清理与更新逻辑。如果没有合适的边界,或者更新失败,开发环境可能回退到整页刷新;这也受开发服务配置影响。
状态能保留多少,取决于框架和更新边界,不是对全部状态的保证。旧定时器、监听器等资源,也需要正确释放。

图:HMR 有人接,才局部换。
知识点详解:文件变化以后,浏览器怎样换掉旧模块?
自动刷新省了按键,HMR 缩小了更新范围
假设你在开发资料填写页。输入了一半,再改一行 CSS。
直接刷新整页,会重新建立页面环境,内存里的输入状态通常也会重新初始化。自动刷新主要省掉了手动按键。
HMR 则希望把更新缩小到受影响的模块。一些 CSS 更新可以直接换样式;组件更新能不能保留状态,还需要框架插件处理。不能把看到的所有效果都算成 HMR 本身的承诺。
更新信息送达,还不等于替换完成
旧模块已经被其他模块使用。光下载一个新文件,并不能回答“使用旧导出的地方该怎么办”。
因此,HMR 运行时还需要查看模块关系。假设页面依赖组件 A,A 又依赖工具 B。B 变了,就检查 B 自己或它的使用者能否接受变化。
能接受更新的地方,就是更新边界。Webpack 使用 accept 等接口表达这层处理关系,框架插件通常会替我们建立部分边界。Webpack 官方说明介绍了更新检查、应用和模块处理过程。
这里是 Webpack 的典型链路。其他工具也能热更新,但不能把 Webpack 的文件格式和 API 原封不动搬过去。
页面没刷新,旧副作用更需要清理
继续假设工具 B 一加载,就启动一个每秒打印日志的定时器。替换时,如果旧定时器没取消,新模块又启动一个,页面里就会同时跑两份。
整页刷新会销毁旧页面环境,模块替换不会替你销毁所有外部资源。所以“不刷新”也意味着我们需要认真管理资源。
清理阶段要释放旧监听器、定时器和连接。Webpack 提供 dispose 钩子,但怎么释放,仍由模块或框架生命周期决定。注册一个钩子,不代表所有资源自动被发现。

图:换模块之前,先收拾旧资源。
保留状态,有合理的边界
改组件内部的一段展示逻辑,与改变导出形态、重写初始化过程,并不一样。插件会按自身规则判断能否保留状态;重新挂载组件甚至整页刷新,有时是合理回退。
排查“为什么这次输入丢了”,要结合框架规则和热更新日志,而不是先认定工具出了错。
面试官继续追问
HMR 是生产环境更新方案吗?
不是。这里讨论开发反馈。生产发布涉及资源版本、缓存、兼容性和回滚,不能直接依赖开发用的热更新链路。
如何验证清理正确?
反复修改同一模块,观察监听器、连接和计时器数量是否持续增长,再检查替换时旧资源是否关闭。只试第一次更新,不容易发现资源叠加。
面试速记卡
- 自动刷新:重建整张页面环境。
- HMR:尽可能替换受影响的模块。
- 更新边界:有人能接受变化,才有机会局部替换。
- 清理:旧副作用不会凭空消失。
- 状态:保留程度取决于框架和边界。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →