ホットモジュールリプレースメント(HMR)は、ページをリロードせずにアプリケーションのモジュールを更新するための技術です。優れた開発体験を提供し、React RouterはViteを使用する場合にHMRをサポートします。
HMRは、更新間でブラウザの状態を維持するために最善を尽くします。たとえば、モーダル内にフォームがあり、すべてのフィールドに入力したとします。従来のライブリロードでは、コードに変更を保存するとすぐにページがハードリフレッシュされ、すべてのフィールドがリセットされます。変更を加えるたびに、モーダルを*再び*開き、フォームに*再び*入力する必要があります。
しかし、HMRを使用すると、すべての状態が*更新間で維持*されます。
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は、action
、headers
、links
、loader
、meta
などのルートエクスポートを管理しますが、ユーザー定義のエクスポートは完全なリロードを引き起こします。
// 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>
);
}
キー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 Fast Refreshは状態<input />
を保持できません❌。
場合によっては、Reactは、変更されている既存のコンポーネントと、追加されている新しいコンポーネントを区別できません。Reactは、これらのケースを明確化し、兄弟要素が変更されたときに変更を追跡するためにkey
が必要です。