主體
分支
主體 (6.23.1)開發中
版本
6.23.1v4/5.xv3.x
errorElement
在本頁

errorElement

當例外狀況發生在 載入器動作 或組件的渲染中,它將會渲染錯誤路徑 (<Route errorElement>)(而非一般路徑的渲染路徑 <Route element>),並透過 useRouteError 來提供錯誤訊息。

如果您不希望指定 React 元件(例如,errorElement={<MyErrorBoundary />}),您可以改指定 ErrorBoundary 組件(例如,ErrorBoundary={MyErrorBoundary}),React Router 將會為您內部呼叫 createElement

此功能僅在使用資料路由器時運作,請參閱 選擇路由器

<Route
  path="/invoices/:id"
  // if an exception is thrown here
  loader={loadInvoice}
  // here
  action={updateInvoice}
  // or here
  element={<Invoice />}
  // this will render instead of `element`
  errorElement={<ErrorBoundary />}
/>;

function Invoice() {
  return <div>Happy {path}</div>;
}

function ErrorBoundary() {
  let error = useRouteError();
  console.error(error);
  // Uncaught ReferenceError: path is not defined
  return <div>Dang!</div>;
}

冒泡

當路由不具有 errorElement 時,錯誤會透過父路由冒泡。這讓您能依需求設定錯誤處理的詳細程度。

在路由樹的最上方放置一個 errorElement,並在一個地方處理應用程式中幾乎所有的錯誤。或者,將它們放在所有路由上,並讓應用程式中沒有錯誤的部分繼續正常渲染。這會提供給使用者更多選項來復原錯誤,而不僅只有強制重新整理 🤞。

預設錯誤元件

我們建議總是在將應用程式交付到正式環境之前提供至少根層級的 errorElement,因為預設 errorElement 的使用者介面很醜,並不是為了供最終使用者使用。

如果您沒有在路由樹中提供一個 errorElement 來處理特定錯誤,錯誤就會向上浮現,並且會由一個預設的 errorElement 來處理,它會列印錯誤訊息和堆疊追蹤。有些人質疑為什麼堆疊追蹤會顯示在正式環境組建中。通常來說,您不希望出於安全性考量在您的正式環境網站中顯示堆疊追蹤。然而,這更適用於伺服器端錯誤(而且 Remix 的確會從伺服器端載入器/動作回應中移除堆疊追蹤)。對於客戶端 react-router-dom 應用程式來說,程式碼反正已經在瀏覽器中可用,所以任何隱藏都只是透過混淆來達成安全性。此外,我們還是希望在主控台中顯示錯誤,因此從使用者介面顯示中移除它並不會隱藏堆疊追蹤的任何資訊。在使用者介面中不顯示它而且不將它記錄到主控台中就表示應用程式開發人員對於正式環境錯誤根本沒有任何資訊,這會產生它自己的問題。因此,我們再次建議您在將您的網站部署到正式環境之前始終新增一個根層級的 errorElement

手動拋出

雖然 errorElement 可用於處理意外錯誤,但它也可以用於處理您預期的例外情況。

特別是在載入器和動作中,當您處理您無法控制的外部資料時,您無法總是計畫資料的存在、服務的可用性或使用者對於它的存取權。在這些情況下,您會可以拋出您自己的例外情況。

這裡是在 載入器 中的「未找到」案例

<Route
  path="/properties/:id"
  element={<PropertyForSale />}
  errorElement={<PropertyError />}
  loader={async ({ params }) => {
    const res = await fetch(`/api/properties/${params.id}`);
    if (res.status === 404) {
      throw new Response("Not Found", { status: 404 });
    }
    const home = await res.json();
    const descriptionHtml = parseMarkdown(
      data.descriptionMarkdown
    );
    return { home, descriptionHtml };
  }}
/>

一旦您知道您無法使用正在載入的資料來顯示路線,您就可以拋出以中斷呼叫堆疊。當資料不存在時,您不必擔心載入器中的其他工作(例如解析使用者的標記化 bio)。只要拋出並退出即可。

這也表示您不必擔心在路由元件中為一堆錯誤分支碼感到憂心。如果您在載入器或動作中拋出,它甚至不會嘗試顯示,因為您的 errorElement 會取而代之顯示。

您可以從載入器或動作中拋出任何東西,就像您可以傳回任何東西一樣:回應(例如先前的範例)、錯誤或純粹物件。

拋出回應

雖然您可以拋出任何東西,而且它將透過 useRouteError 提供給您,如果您拋出 Response,React Router 會在將它傳回您的元件之前自動解析回應資料。

此外,isRouteErrorResponse 讓您可以檢查界線中的特定類型。結合 json,您可以輕易地丟出帶有些許資料的回覆,並在您的界線中呈現不同的情況

import { json } from "react-router-dom";

function loader() {
  const stillWorksHere = await userStillWorksHere();
  if (!stillWorksHere) {
    throw json(
      {
        sorry: "You have been fired.",
        hrEmail: "hr@bigco.com",
      },
      { status: 401 }
    );
  }
}

function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error) && error.status === 401) {
    // the response json is automatically parsed to
    // `error.data`, you also have access to the status
    return (
      <div>
        <h1>{error.status}</h1>
        <h2>{error.data.sorry}</h2>
        <p>
          Go ahead and email {error.data.hrEmail} if you
          feel like this is a mistake.
        </p>
      </div>
    );
  }

  // rethrow to let the parent error boundary handle it
  // when it's not a special case for this route
  throw error;
}

這使得您可以建立一個通用的錯誤界線(通常在根路由),來處理許多情況

function RootBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    if (error.status === 404) {
      return <div>This page doesn't exist!</div>;
    }

    if (error.status === 401) {
      return <div>You aren't authorized to see this</div>;
    }

    if (error.status === 503) {
      return <div>Looks like our API is down</div>;
    }

    if (error.status === 418) {
      return <div>🫖</div>;
    }
  }

  return <div>Something went wrong</div>;
}

抽象

當您知道無法繼續執行您所進行的資料載入路徑時,拋出這個模式,可以相當簡單地正確處理例外狀況。

想像一個類似這樣的函式,用於取得使用者網路權杖,用於授權的請求

async function getUserToken() {
  const token = await getTokenFromWebWorker();
  if (!token) {
    throw new Response("", { status: 401 });
  }
  return token;
}

不論哪一個載入器或動作使用那個函式,它都會停止在目前呼叫堆疊中執行程式碼,反之將應用程式傳送至錯誤路徑。

現在讓我們新增一個用於擷取專案的函式

function fetchProject(id) {
  const token = await getUserToken();
  const response = await fetch(`/projects/${id}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (response.status === 404) {
    throw new Response("Not Found", { status: 404 });
  }

  // the fetch failed
  if (!response.ok) {
    throw new Error("Could not fetch project");
  }
}

多虧了 getUserToken,這個程式碼可以假設它會取得一個權杖。如果沒有權杖,則會呈現錯誤路徑。然後如果專案不存在,則不論哪一個載入器呼叫這個函式,它都會拋出 404 到 errorElement。最後,如果擷取完全失敗,則會傳送一個錯誤。

在您發現「我沒有我需要的東西」的任何時間,您都可以簡單地 throw,並知道您仍然會為終端使用者呈現有用的東西。

讓我們把它放在一個路由中

<Route
  path="/"
  element={<Root />}
  errorElement={<RootBoundary />}
>
  <Route
    path="projects/:projectId"
    loader={({ params }) => fetchProject(params.projectId)}
    element={<Project />}
  />
</Route>

專案路由完全不需要考慮錯誤。在載入器公用程式函式(像是 fetchProjectgetUserToken)在某些事情不對勁時都會拋出,而 RootBoundary 處理所有情況的情況下,專案路由可以專注於快樂的路徑。

文件和範例 CC 4.0