热模块替换
本页内容

热模块替换

热模块替换是一种无需重新加载页面即可更新应用中模块的技术。它提供了出色的开发者体验,并且在与 Vite 一起使用时,React Router 支持该技术。

HMR 会尽力在更新时保留浏览器状态。例如,假设你在一个模态框中有一个表单,并且你填写了所有字段。一旦你保存了对代码的任何更改,传统的实时重载就会硬刷新页面,导致所有这些字段被重置。每次你做出更改,你都必须再次打开模态框并再次填写表单。

但使用 HMR,所有这些状态都可以在更新之间保留。

React 快速刷新

React 已经有了通过其虚拟 DOM 来响应用户交互(如点击按钮)更新 DOM 的机制。如果 React 也能处理响应代码更改更新 DOM,那不是很好吗?

这正是React 快速刷新的意义所在!当然,React 主要关注组件,而不是通用的 JavaScript 代码,因此 React 快速刷新仅处理导出的 React 组件的热更新。

但 React 快速刷新确实有一些你应该注意的限制。

类组件状态

React 快速刷新不保留类组件的状态。这包括内部返回类的 HOC(高阶组件)。

export class ComponentA extends Component {} // ❌

export const ComponentB = HOC(ComponentC); // ❌ Won't work if HOC returns a class component

export function ComponentD() {} // ✅
export const ComponentE = () => {}; // ✅
export default function ComponentF() {} // ✅

命名函数组件

函数组件必须是命名的,而不是匿名的,React 快速刷新才能跟踪其变化。

export default () => {}; // ❌
export default function () {} // ❌

const ComponentA = () => {};
export default ComponentA; // ✅

export default function ComponentB() {} // ✅

支持的导出

React 快速刷新只能处理组件导出。虽然 React Router 为你管理路由导出,例如 actionheaderslinksloadermeta,任何用户自定义的导出都会导致完全重新加载。

// These exports are handled by the React Router Vite plugin
// to be HMR-compatible
export const meta = { title: "Home" }; // ✅
export const links = [
  { rel: "stylesheet", href: "style.css" },
]; // ✅

// These exports are removed by the React Router Vite plugin
// so they never affect HMR
export const headers = { "Cache-Control": "max-age=3600" }; // ✅
export const loader = async () => {}; // ✅
export const action = async () => {}; // ✅

// This is not a route module export, nor a component export,
// so it will cause a full reload for this route
export const myValue = "some value"; // ❌

export default function Route() {} // ✅

👆 路由无论如何都不应该像那样导出随机值。如果你想在不同路由之间重用值,请将它们放在自己的非路由模块中。

export const myValue = "some value";

更改 Hooks

当组件中添加或移除 Hooks 时,React 快速刷新无法跟踪其变化,导致仅在下次渲染时进行完全重新加载。在 Hooks 更新后,后续更改应该会再次触发热更新。例如,如果你给组件添加一个 useState,你可能会在下次渲染时丢失该组件的本地状态。

此外,如果你正在解构 Hook 的返回值,如果解构的 key 被移除或重命名,React 快速刷新将无法保留组件的状态。例如:

export default function Component({ loaderData }) {
  const { pet } = useMyCustomHook();
  return (
    <div>
      <input />
      <p>My dog's name is {pet.name}!</p>
    </div>
  );
}

如果你将 key pet 改为 dog

 export default function Component() {
-  const { pet } = useMyCustomHook();
+  const { dog } = useMyCustomHook();
   return (
     <div>
       <input />
-      <p>My dog's name is {pet.name}!</p>
+      <p>My dog's name is {dog.name}!</p>
     </div>
   );
 }

那么 React 快速刷新将无法保留 <input /> 的状态 ❌。

组件 Key

在某些情况下,React 无法区分现有组件的更改和新组件的添加。React 需要 key 来区分这些情况,并在同级元素被修改时跟踪更改。

文档和示例 CC 4.0