概述
Vite 提供了一套原生 ESM 的 HMR API。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。当通过 create-vite
创建应用程序时,所选模板已经预先配置了相关的集成。
HMR API
Vite 通过特殊的 import.meta.hot
对象暴露手动 HMR API。
interface ImportMeta {
readonly hot?: ViteHotContext
}
interface ViteHotContext {
readonly data: any
accept(): void
accept(cb: (mod: ModuleNamespace | undefined) => void): void
accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void
accept(
deps: readonly string[],
cb: (mods: Array<ModuleNamespace | undefined>) => void,
): void
dispose(cb: (data: any) => void): void
prune(cb: (data: any) => void): void
invalidate(message?: string): void
on<T extends string>(
event: T,
cb: (payload: InferCustomEventPayload<T>) => void,
): void
off<T extends string>(
event: T,
cb: (payload: InferCustomEventPayload<T>) => void,
): void
send<T extends string>(event: T, data?: InferCustomEventPayload<T>): void
}
代码结构概述
代码中定义了两个接口:ImportMeta
和 ViteHotContext
。ImportMeta
是在模块上下文中使用的,ViteHotContext
则是 Vite 的 HMR 相关的接口,包含了一些允许你在模块更新时执行特定操作的方法。
ImportMeta
接口
interface ImportMeta {
readonly hot?: ViteHotContext;
}
hot
属性是 ViteHotContext
类型的可选属性。当 Vite 在开发模式下运行时,这个属性会被填充,用于处理 HMR 相关操作。如果你在生产环境或 HMR 未启用的情况下访问 import.meta.hot
,它将是 undefined
。
ViteHotContext
接口
ViteHotContext
定义了与 HMR 相关的各种方法,这些方法用于处理模块的更新、清理以及自定义事件。
方法说明
-
accept()
- 用于接受模块更新。当模块更新时,Vite 将使用最新的模块重新加载。你可以提供一个回调函数来处理更新后的模块,或仅调用
accept()
以自动接受更新。 - 示例:
if (import.meta.hot) { import.meta.hot.accept((mod) => { console.log('模块更新了', mod); }); }
- 用于接受模块更新。当模块更新时,Vite 将使用最新的模块重新加载。你可以提供一个回调函数来处理更新后的模块,或仅调用
-
dispose(cb)
- 注册一个回调函数,当模块被替换或页面刷新时执行。可以在这里进行资源清理或保存一些状态信息。
- 示例:
if (import.meta.hot) { import.meta.hot.dispose((data) => { data.someState = currentState; }); }
-
prune(cb)
- 当模块被 HMR 剪枝时(意味着模块将不再被使用),会调用这个回调函数。可以用它来处理一些清理工作。
- 示例:
if (import.meta.hot) { import.meta.hot.prune(() => { console.log('模块已被剪枝'); }); }
-
invalidate(message)
- 强制使当前模块失效,并触发 HMR 更新。可以选择传递一个信息字符串,说明为什么需要失效。
- 示例:
if (import.meta.hot) { import.meta.hot.invalidate('模块需要更新'); }
-
on(event, cb)
和off(event, cb)
on
方法用于监听自定义的 HMR 事件,off
方法用于取消监听这些事件。可以通过send
方法发送自定义事件。- 示例:
if (import.meta.hot) { import.meta.hot.on('my-custom-event', (payload) => { console.log('收到自定义事件', payload); }); }
-
send(event, data)
- 发送自定义 HMR 事件,并附带数据。
- 示例:
if (import.meta.hot) { import.meta.hot.send('my-custom-event', { someData: 123 }); }
结合示例说明
假设在一个项目中使用了一个组件,并且希望在组件更新时能保存它的状态,代码如下:
<script setup>
let currentState = {count: 0}; // 初始化状态
if (import.meta.hot) {
// 检查是否存在之前保存的状态,并进行恢复
if (import.meta.hot.data && import.meta.hot.data.currentState) {
currentState = import.meta.hot.data.currentState;
}
// 接受模块更新
import.meta.hot.accept((mod) => {
console.log('模块更新了', mod);
});
// 保存当前状态,确保在模块被替换前调用
import.meta.hot.dispose((data) => {
data.currentState = currentState;
});
}
function increment() {
currentState.count += 1;
console.log(currentState.count);
}
</script>
<template>
<button @click="increment">count++</button>
</template>
<style scoped>
</style>
在这个示例中,通过 dispose
方法保存组件的状态,并在模块更新后通过 accept
方法重新加载模块时恢复该状态。这就避免了在模块热更新时丢失状态。
项目运行:
模块更新:
如果将外部引入的模块删掉并保存,则会显示 page reload,也就是页面重新刷新了一下。
hmr 只会在开发环境生效( 生产环境 import.meta.hot 为 undefined ),在生产环境里边它是不存在的。就会被 tree shaking 给优化掉。