React Router v7 已發佈。 查看文件
熱模組替換
本頁內容

熱模組替換

熱模組替換是一種在您的應用程式中更新模組而無需重新載入頁面的技術。它提供了良好的開發者體驗,而 Remix 開箱即支援此功能。

值得注意的是,HMR 會盡力在更新之間保留瀏覽器狀態。如果您在模態視窗內有表單,並且您填寫了所有欄位,傳統的即時重新載入會硬重新整理頁面。因此,您將遺失表單中的所有資料。每次進行變更時,您都必須再次開啟模態視窗並再次填寫表單。😭

但是有了 HMR,所有這些狀態都會在更新之間保留。✨

React 快速刷新

React 已經有透過其虛擬 DOM 來更新 DOM 的機制,以回應使用者互動,例如點擊按鈕。如果 React 也能處理回應程式碼變更來更新 DOM,那不是很棒嗎?

這正是 React 快速刷新 的全部意義所在!當然,React 的重點是元件,而不是一般的 JavaScript 程式碼,因此 RFR 本身只會處理導出 React 元件的熱更新。

但 React 快速刷新確實有一些您應該注意的限制。

類別元件狀態

React 快速刷新不會保留類別元件的狀態。這包括內部傳回類別的高階元件。

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 快速刷新只能處理元件導出。雖然 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() {} // ✅

👆 路由可能無論如何都不應該導出像那樣的隨機值。如果您想在路由之間重複使用值,請將它們放在它們自己的非路由模組中

export const myValue = "some value";

變更 Hook

當從元件中新增或移除 Hook 時,React Fast Refresh 無法追蹤元件的變更,導致僅為了下一次渲染而完整重新載入。在 Hook 更新後,變更應再次導致熱更新。例如,如果您將 useLoaderData 新增到您的元件中,您可能會在該次渲染中遺失該元件的本機狀態。

此外,如果您正在解構 Hook 的回傳值,如果已移除或重新命名解構的鍵,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 /> 的狀態 ❌。

作為一種解決方法,您可以避免解構,而是直接使用 Hook 的回傳值

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 來消除這些情況的歧義並追蹤變更。

文件和範例授權於 MIT