Hot Reload — 是一种技术,通过它,源代码中的变更可以应用到正在运行的应用程序,而无需完全重新加载页面且不会丢失当前状态。与仅仅刷新整个页面的 Live Reload 不同,热重新加载只在运行时替换已修改的模块。据 Webpack, 2024,Hot Module Replacement (HMR) 通过保持组件状态,可将开发循环时间缩短达 80%。
主要观点
Hot Reload — 是一种开发机制,通过它,源文件中的变更会立即反映到正在运行的应用程序中,而无需重新启动应用程序。开发者在编辑器中编辑代码,保存文件,结果立即在浏览器或仿真器中可见。
该技术的关键特性是保持 应用程序状态。内存中的变量、表单中的数据、当前导航 — 一切保持不变。开发者无需重复操作序列来返回所需屏幕。
在 Web 开发中,Hot Reload 通过 Hot Module Replacement (HMR) 机制实现。打包器监视文件变更,确定受影响的模块,并通过 WebSocket 将更新后的代码发送到浏览器。浏览器无需重新加载页面即可替换这些模块。
随着单页面应用程序 (SPA) 的普及,Hot Reload 的受欢迎程度激增,因为完全重新加载页面会销毁客户端应用程序的整个状态。React、Vue、Angular 及其他框架将 HMR 作为主要开发模式进行推荐。
HMR 机制由四个阶段组成。服务器端的打包器编译已修改的模块,创建带有新版本代码的 JSON 补丁,并通过 WebSocket 连接发送到浏览器。浏览器端的 HMR 运行时接收补丁,将旧模块替换为新模块,并通知模块的订阅者。
Webpack Dev Server 使用 WebSocket 与客户端进行通信。当文件发生变更时,打包器会创建带有变更清单的 hot-update.js 和 hot-update.json。客户端通过 JSONP 自动加载这些文件。
// webpack.config.js - HMR 配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devServer: {
hot: true,
liveReload: false
},
plugins: [new HtmlWebpackPlugin()]
};
浏览器中的 HMR 运行时检查被替换的模块是否支持 accept。如果模块声明了 module.hot.accept(),运行时将在原位替换它并执行更新回调。如果没有声明 accept,HMR 将降级到上一级,即父模块。
在 React Fast Refresh 中,这个逻辑已内置在框架层面 — 无需在每个文件中手动编写 module.hot.accept。Babel 插件 react-refresh/babel 添加能够在不丢失状态的情况下安全替换组件的代码。
这三个术语常常被混淆,但它们在行为上根本不同。Live Reload 在每次变更时重新加载整个页面 — 所有状态丢失。Full Reload 发生在开发服务器完全重启时,不仅丢失客户端状态,还丢失服务器状态。
| 类型 | 重新加载页面 | 保持状态 | 速度 |
|---|---|---|---|
| Hot Reload (HMR) | 否 | 是 | 即时 |
| Live Reload | 是 | 否 | 1-3 秒 |
| Full Reload | 是 | 否 | 3-10 秒 |
在 Hot 和 Live 之间的选择直接影响 开发速度。在 HMR 中,“修改代码 → 查看结果” 循环只需 50-200 毫秒。在 Live Reload 中 — 1-3 秒,加上恢复上下文的时间。在一个工作日内,这个差异可以节省几十分钟的时间。
React Fast Refresh — 是针对 React 组件进行了优化的 Hot Reload 官方实现。它取代了旧的 React Hot Loader 机制,该机制不稳定且需要手动配置。Fast Refresh 通过 react-refresh/babel 内置于 Create React App、Next.js、Vite 和 Webpack。
Fast Refresh 在代码变更时正确保持函数组件的状态。如果仅仅更改 JSX 标记 — useState 和其他 Hook 会保持它们的值。如果 Hook 的逻辑发生变更 — 组件将重新挂载。
// 示例 - JSX 变更时状态保持
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
如果新代码包含 编译错误,Fast Refresh 不会保持状态。如果组件不是默认导出而是命名导出 (named export),状态也可能无法保持。建议使用默认导出 (default export) 来导出组件,并在需要保证状态保持时将导出用 memo 进行包装。
现代打包器提供 HMR。Webpack — HMR 的先驱,通过 devServer.hot 提供灵活配置。Vite 使用原生 ESM 和 esbuild 实现无需打包的即时 HMR。Parcel — 零配置 HMR。Turbopack — Vercel 推出的新一代打包器,基于 Rust 实现 HMR。
| 工具 | HMR 类型 | 配置 | 生态系统 |
|---|---|---|---|
| Webpack 5 | 通过 WebSocket 的 HMR | devServer.hot: true | React, Vue, Angular |
| Vite | 原生 ESM HMR | 开箱即用 | React, Vue, Svelte |
| Parcel 2 | 零配置 HMR | 不需要 | React, Vue, TS |
| Turbopack | 渐进式 HMR | 开箱即用 | Next.js |
Vite 以速度见长 — Vite 中的 HMR 通过浏览器的原生 ES 模块工作。当文件变更时,Vite 仅通过 import() 发送该模块,而无需重新编译整个项目。Webpack 对每个模块进行完整编译,这在大型项目中会减慢 HMR。
让我们看看不同打包器的 HMR 配置。在 Webpack 中需要在 devServer 中设置 hot: true 并添加 react-refresh/babel。在 Vite 中,HMR 可以与任何预设开箱即用。在 Next.js 中,Fast Refresh 通过涡轮编译器默认启用。
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
hmr: {
port: 3001
}
}
});
// webpack.config.js
const ReactRefreshWebpackPlugin =
require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
mode: 'development',
devServer: { hot: true },
plugins: [new ReactRefreshWebpackPlugin()],
module: {
rules: [
{
test: /\.jsx?$/u,
use: 'babel-loader'
}
]
}
};
HMR 并非在所有场景下都能正确工作。带有动态类的 CSS 模块可能无法应用。全局变量和单例 (Redux store、路由器) 在重新加载时可能会重置。重型库在 HMR 时因为无垃圾回收的模块替换而可能导致内存泄漏。
HMR 问题的解决方案 — 将 热代码封装在无副作用的纯组件中。副作用 (WebSocket 订阅、定时器) 应在 useEffect 中清除。对于 Redux,建议使用将状态保存到 sessionStorage 的持久化器。CSS Modules 或 styled-components 等 CSS 解决方案无需额外配置即可与 HMR 正确工作。
另一个局限性 — 生产环境下的调试:HMR 在生产构建中被故意关闭。源代码被最小化,热替换在生产环境下是不可能的。生产环境下的错误调试使用 source maps 和日志记录,而非 HMR。
常见问题
Hot Reload 仅替换已修改的模块,无需重新加载页面并保持状态。Live Reload 会重新加载整个页面 — 状态丢失,应用程序从初始屏幕重新启动。
在 Create React App 和 Vite 项目中,HMR 默认启用。在 Webpack 中需要添加 devServer: { hot: true } 和 @pmmmwh/react-refresh-webpack-plugin。在 Next.js 中,Fast Refresh 无需配置即可工作。
原因是 编译错误、使用命名导出而非默认导出、Hook 签名变更或直接纠细状态。React Fast Refresh 仅在必要时才会安全重置组件以确保正确运行。
可以,所有现代打包器都支持与 TypeScript 一起使用 HMR。Vite 使用 esbuild 进行 TS 翻译。Webpack 使用 ts-loader 或带有 @babel/preset-typescript 的 babel。Fast Refresh 能与类型化组件正确工作。
检查 浏览器控制台 中的 WebSocket 连接错误。确保 devServer.hot: true 已启用。对于 Vite,检查 HMR 端口。如果问题仍然存在 — 执行完全重新加载页面 (F5) 并检查打包器控制台。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。