模块热替换
On this page

模块热替换

¥Hot Module Replacement

热模块替换是一种无需重新加载页面即可更新应用中模块的技术。这会带来很棒的开发者体验,而且 Remix 开箱即用。

¥Hot Module Replacement is a technique for updating modules in your app without needing to reload the page. It's a great developer experience, and Remix supports it out of the box.

值得注意的是,HMR 会尽力在更新期间保留浏览器状态。如果你的 form 位于模态框内,并且你填写了所有字段,则传统的实时刷新会难以刷新页面。所以你会丢失表单中的所有数据。每次进行更改时,你都必须再次打开模态框并再次填写表单。😭

¥Notably, HMR does its best to preserve browser state across updates. If you have a form within a modal, and you fill out all the fields, traditional live reload would hard refresh the page. So you'd lose all the data in the form. Every time you make a change, you'd have to open up the modal again and fill out the form again. 😭

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

¥But with HMR, all that state is preserved across updates. ✨

React 快速刷新

¥React Fast Refresh

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

¥React already has mechanisms for updating the DOM via its virtual DOM in response to user interactions like clicking a button. Wouldn't it be great if React could handle updating the DOM in response to code changes too?

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

¥That's exactly what React Fast Refresh is all about! Of course, React is all about components, not general JavaScript code, so RFR by itself only handles hot updates for exported React components.

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

¥But React Fast Refresh does have some limitations that you should be aware of.

类组件状态

¥Class Component State

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

¥React Fast Refresh does not preserve state for class components. This includes higher-order components that internally return classes:

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() {} // ✅

命名函数组件

¥Named Function Components

函数组件必须命名,而不是匿名,以便 React Fast Refresh 跟踪更改:

¥Function components must be named, not anonymous, for React Fast Refresh to track changes:

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

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

export default function ComponentB() {} // ✅

支持的导出

¥Supported Exports

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

¥React Fast Refresh can only handle component exports. While Remix manages special route exports like action, headers, links, loader, and meta for you, any user-defined exports will cause full reloads:

// 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() {} // ✅

👆 路由可能不应该像这样导出随机值。如果你想跨路由复用值,请将它们粘贴到各自的非路由模块中:

¥👆 Routes probably shouldn't be exporting random values like that anyway. If you want to reuse values across routes, stick them in their own non-route module:

export const myValue = "some value";

更改钩子函数

¥Changing Hooks

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

¥React Fast Refresh cannot track changes for a component when hooks are being added or removed from it, causing full reloads just for the next render. After the hooks have been updated, changes should result in hot updates again. For example, if you add useLoaderData to your component, you may lose state local to that component for that render.

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

¥Additionally, if you are destructuring a hook's return value, React Fast Refresh will not be able to preserve state for the component if the destructured key is removed or renamed. For example:

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

¥If you change the key stuff to 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 />❌。

¥then React Fast Refresh will not be able to preserve state <input /> ❌.

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

¥As a workaround, you could refrain from destructuring and instead use the hook's return value directly:

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

¥Now if you change the key stuff to 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 />)是已更改元素的同级元素,你可能需要使用下一节中描述的组件键。

¥Then React Fast Refresh will preserve state for the <input />, though you may need to use component keys as described in the next section if the stateful element (e.g. <input />) is a sibling of the changed element.

组件键值

¥Component Keys

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

¥In some cases, React cannot distinguish between existing components being changed and new components being added. React needs keys to disambiguate these cases and track changes when sibling elements are modified.

Remix v2.17 中文网 - 粤ICP备13048890号