コンテンツにスキップ

Hot Module Replacement

このコンテンツはまだ日本語訳がありません。

模块热替换(HMR)

模块热替换是一种无需重新加载页面即可更新应用中模块的技术。 这是一种出色的开发人员体验,并且 Remix 开箱即用地支持它。

值得注意的是,HMR 会尽力在更新过程中保留浏览器状态。 如果您在模态框内有表单,并且您填写了所有字段,传统的实时重新加载会很难刷新页面。 因此,您会丢失表单中的所有数据。 每次进行更改时,您都必须再次打开模态框并再次填写表单。😭

但使用 HMR,所有状态都会在更新过程中保留。✨

React 快速刷新

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

这正是 React Fast Refresh 的意义所在! 当然,React 的核心是组件,而不是通用的 JavaScript 代码,因此 RFR 本身只能处理导出的 React 组件的热更新。

但是 React Fast Refresh 确实有一些您应该注意的限制。

类组件状态

React Fast Refresh 不会保留类组件的状态。 这包括内部返回类的高阶组件:

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 Fast Refresh 跟踪更改:

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

支持导出

React Fast Refresh 只能处理组件导出。虽然 Remix 可以为您管理特殊路由导出,例如 actionheaderslinksloadermeta,但任何用户定义的导出都会导致完全重新加载:

// These exports are handled by the Remix 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 Remix 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 Remix export, nor a component export,
// so it will cause a full reload for this route
export const myValue = "some value"; // ❌
export default function Route() {} // ✅

👆 无论如何,路由可能不应该导出这样的随机值。 如果您想跨路由重用值,请将它们粘贴在自己的非路由模块中:

my-custom-value.ts
export const myValue = "some value";

更改 Hooks

当添加或删除组件中的钩子时,React Fast Refresh 无法跟踪组件的更改,从而导致下一次渲染时完全重新加载。更新钩子后,更改应再次导致热更新。例如,如果您将 useLoaderData 添加到组件,您可能会丢失该渲染时该组件的本地状态。

此外,如果你正在解构钩子的返回值,则如果解构的键被删除或重命名,React Fast Refresh 将无法保留组件的状态。 例如:

export const loader = async () => {
return json({ stuff: "some things" });
};
export default function Component() {
const { stuff } = useLoaderData<typeof loader>();
return (
<div>
<input />
<p>{stuff}</p>
</div>
);
}

如果将键 stuff更改为 things`:

export const loader = async () => {
return json({ stuff: "some things" })
return json({ things: "some things" })
}
export default Component() {
const { stuff } = useLoaderData<typeof loader>()
const { things } = useLoaderData<typeof loader>()
return (
<div>
<input />
<p>{stuff}</p>
<p>{things}</p>
</div>
)
}

那么 React Fast Refresh 将无法保存状态 <input />❌。

作为一种解决方法,你可以避免解构,而是直接使用钩子的返回值:

export const loader = async () => {
return json({ stuff: "some things" });
};
export default function Component() {
const data = useLoaderData<typeof loader>();
return (
<div>
<input />
<p>{data.stuff}</p>
</div>
);
}

现在如果你将键 stuff 更改为 things

export const loader = async () => {
return json({ stuff: "some things" })
return json({ things: "some things" })
}
export default Component() {
const data = useLoaderData<typeof loader>()
return (
<div>
<input />
<p>{data.stuff}</p>
<p>{data.things}</p>
</div>
)
}

然后,React Fast Refresh 将保留 <input /> 的状态,但如果有状态元素(例如 <input />)是已更改元素的兄弟元素,则可能需要使用下一节中描述的组件键。

组件键

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