原子化拆分需每个 export 职责单一、边界清晰,配合 module.hot.accept 精准更新对应导出项,并确保模块纯净无副作用,以实现 HMR 最小化替换。

直接用 export 声明做原子化拆分,核心不是“多写几个 export”,而是让每个导出项职责单一、边界清晰、可独立更新。这样 HMR 才能精准替换变更模块,避免牵连无关逻辑。
按功能粒度拆分为独立导出项
把一个大模块里混杂的工具函数、状态管理、副作用逻辑,拆成多个命名导出,每个只做一件事。例如:
// utils.js
export const formatDate = (date) => new Date(date).toLocaleString();
export const debounce = (fn, delay) => { /* ... */ };
export const apiClient = { get: () => fetch(...) };
这样修改 formatDate 时,HMR 只需更新该函数,不会触发 debounce 或 apiClient 的重新执行或重载。
- 避免将多个不相关逻辑塞进同一个
export default对象中 - 拒绝
export * from './xxx'这类批量导出,它会模糊模块边界,使 HMR 无法判断哪些子项实际被使用 - 每个
export应有明确语义,比如useCartStore、validateEmail、saveToLocalStorage
配合 module.hot.accept 精准接管更新逻辑
仅靠拆分 export 不够,还需在消费端显式声明“哪个导出变了,该怎么换”。例如:
import { formatDate, debounce } from './utils';
<p>// 初始使用
const display = formatDate(new Date());</p><p>if (module.hot) {
module.hot.accept('./utils', ({ formatDate: newFormatDate }) => {
// 只更新用到的函数,不干扰 debounce 或其他未引用项
display = newFormatDate(new Date());
});
}</p>- 接受回调中解构出具体变更的导出名(如
{ formatDate: newFormatDate }),而非整个模块 - 只重建受影响的 UI 片段或状态实例,避免重跑整个初始化流程
- 对未被
import的导出项,即使文件变更也不会触发其 accept 回调
避免副作用和共享状态污染模块纯净性
HMR 要求模块是“可置换”的——替换后不能残留旧状态或副作用。因此导出项本身应尽量无副作用:
- 导出纯函数(如
export const sum = (a, b) => a + b)天然适合热替换 - 避免导出含闭包状态的工厂函数(如
export const createCounter = () => { let count = 0; return () => count++ }),除非你主动在 accept 中销毁旧实例 - 全局监听、定时器、DOM 挂载等副作用,应封装在单独的导出函数中,并在 accept 回调里显式清理再重建
搭配 ESM 静态分析提升构建期识别精度
Webpack 和 Vite 的 HMR 依赖 ESM 的静态结构。确保:
- 使用
export const xxx = ...或export function xxx(),而非动态赋值(如export {}后再xxx.xxx = ...) - 入口或父模块只
import { xxx }明确需要的项,不写import * as utils - 开启 Webpack 的
optimization.usedExports: true,帮助构建工具识别哪些导出真正被使用,进一步缩小 HMR 影响范围










