ホットモジュールリプレースメント
このページの内容

ホットモジュールリプレースメント

ホットモジュールリプレースメント(HMR)は、ページをリロードせずにアプリケーションのモジュールを更新するための技術です。優れた開発体験を提供し、React RouterはViteを使用する場合にHMRをサポートします。

HMRは、更新間でブラウザの状態を維持するために最善を尽くします。たとえば、モーダル内にフォームがあり、すべてのフィールドに入力したとします。従来のライブリロードでは、コードに変更を保存するとすぐにページがハードリフレッシュされ、すべてのフィールドがリセットされます。変更を加えるたびに、モーダルを*再び*開き、フォームに*再び*入力する必要があります。

しかし、HMRを使用すると、すべての状態が*更新間で維持*されます。

React高速リフレッシュ

Reactには、ボタンのクリックなどのユーザー操作に応じて、仮想DOMを介してDOMを更新するメカニズムが既に備わっています。コード変更に応じてDOMの更新もReactが処理できたら素晴らしいと思いませんか?

まさにそれがReact Fast Refreshの目的です!もちろん、Reactは一般的なJavaScriptコードではなくコンポーネントを扱うため、React Fast Refreshはエクスポートされた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は、コンポーネントのエクスポートのみを処理できます。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";

フックの変更

React Fast Refreshは、コンポーネントにフックが追加または削除された場合、そのコンポーネントの変更を追跡できず、次のレンダリングのためだけに完全なリロードが発生します。フックが更新された後、変更は再びホットアップデートされるはずです。たとえば、コンポーネントにuseStateを追加すると、次のレンダリングでそのコンポーネントのローカル状態が失われる可能性があります。

さらに、フックの戻り値を分割代入している場合、分割代入されたキーが削除または名前変更されると、React Fast Refreshはコンポーネントの状態を保持できません。例えば

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

キーpetdogに変更した場合

 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 Fast Refreshは状態<input />を保持できません❌。

コンポーネントキー

場合によっては、Reactは、変更されている既存のコンポーネントと、追加されている新しいコンポーネントを区別できません。Reactは、これらのケースを明確化し、兄弟要素が変更されたときに変更を追跡するためにkeyが必要です

ドキュメントと例 CC 4.0