跳至主要內容
版本:v18.0.0

使用 ErrorBoundaries 處理錯誤狀態

您可能已經注意到,我們提到使用 usePreloadedQuery 會從伺服器擷取(或正在擷取)查詢中的資料,但我們沒有詳細說明如何在擷取期間發生錯誤時,呈現 UI 來顯示錯誤。我們將在本節中涵蓋這一點。

我們可以利用 Error Boundary 元件來捕捉渲染期間發生的錯誤(由於網路錯誤或任何類型的錯誤),並在發生錯誤時渲染替代的錯誤 UI。它的運作方式與 Suspense 類似,透過將元件樹包在錯誤邊界中,我們可以指定發生錯誤時要如何反應,例如渲染回退 UI。

錯誤邊界 只是實作靜態 getDerivedStateFromError 方法的元件

const React = require('React');

type State = {error: ?Error};

class ErrorBoundary extends React.Component<Props, State> {
static getDerivedStateFromError(error): State {
// Set some state derived from the caught error
return {error: error};
}
}
/**
* App.react.js
*/

const ErrorBoundary = require('ErrorBoundary');
const React = require('React');

const MainContent = require('./MainContent.react');
const SecondaryContent = require('./SecondaryContent.react');

function App() {
return (
// Render an ErrorSection if an error occurs within
// MainContent or Secondary Content
<ErrorBoundary fallback={error => <ErrorUI error={error} />}>
<MainContent />
<SecondaryContent />
</ErrorBoundary>
);
}
  • 我們可以利用錯誤邊界來包裝子樹,並在該子樹中發生錯誤時顯示不同的 UI。當錯誤發生時,將會渲染指定的 fallback,而不是邊界內的內容。
  • 請注意,我們也可以透過在不同層級使用錯誤邊界包裝元件來控制渲染錯誤 UI 的細微程度。在此範例中,如果 MainContentSecondaryContent 中發生任何錯誤,我們將會渲染 ErrorSection 來取代整個應用程式內容。

錯誤發生後的重試

當使用 useQueryLoader / loadQuery

當使用 useQueryLoader/loadQuery 來擷取查詢時,為了在發生錯誤後重試,您可以再次呼叫 loadQuery,並將「新」的查詢參考傳遞給 usePreloadedQuery

/**
* ErrorBoundaryWithRetry.react.js
*/

const React = require('React');

// NOTE: This is NOT actual production code;
// it is only used to illustrate example
class ErrorBoundaryWithRetry extends React.Component<Props, State> {
state = {error: null};

static getDerivedStateFromError(error): State {
return {error: error};
}

_retry = () => {
// This ends up calling loadQuery again to get and render
// a new query reference
this.props.onRetry();
this.setState({
// Clear the error
error: null,
});
}

render() {
const {children, fallback} = this.props;
const {error} = this.state;
if (error) {
if (typeof fallback === 'function') {
return fallback({error, retry: this._retry});
}
return fallback;
}
return children;
}
}
  • 當發生錯誤時,我們會渲染提供的 fallback
  • 當呼叫 retry 時,我們會清除錯誤,並再次呼叫 loadQuery。這將會再次擷取查詢並提供我們新的查詢參考,然後我們可以將其傳遞給 usePreloadedQuery
/**
* App.react.js
*/

const ErrorBoundaryWithRetry = require('ErrorBoundaryWithRetry');
const React = require('React');

const MainContent = require('./MainContent.react');

const query = require('__generated__/MainContentQuery.graphql');

// NOTE: This is NOT actual production code;
// it is only used to illustrate example
function App(props) {
// E.g., initialQueryRef provided by router
const [queryRef, loadQuery] = useQueryLoader(query, props.initialQueryRef);

return (
<ErrorBoundaryWithRetry
// On retry we call loadQuery again, which will update
// the value of queryRef from useQueryLoader with a new
// fresh query reference
onRetry={() => loadQuery(/* ... */)}
fallback={({error, retry}) =>
<>
<ErrorUI error={error} />
{/* Render a button to retry; this will attempt to re-render the
content inside the boundary, i.e. the query component */}
<Button onPress={retry}>Retry</Button>
</>
}>
{/* The value of queryRef will be updated after calling
loadQuery again */}
<MainContent queryRef={queryRef} />
</ErrorBoundaryWithRetry>
);
}

/**
* MainContent.react.js
*/
function MainContent(props) {
const data = usePreloadedQuery(
graphql`...`,
props.queryRef
);

return (/* ... */);
}
  • 此範例程式碼中的範例錯誤邊界將會為 fallback 提供一個 retry 函式,我們可以利用它來清除錯誤、重新載入查詢,並使用可以傳遞給使用 usePreloadedQuery 的元件的新查詢參考來重新渲染。該元件將會使用新的查詢參考,並在新的網路請求上視需要暫停。

當使用 useLazyLoadQuery

當使用 useLazyLoadQuery 來擷取查詢時,為了在發生錯誤後重試,您可以嘗試重新掛載「並且」透過傳遞新的 fetchKey 來重新評估查詢元件

/**
* ErrorBoundaryWithRetry.react.js
*/

const React = require('React');

// NOTE: This is NOT actual production code;
// it is only used to illustrate example
class ErrorBoundaryWithRetry extends React.Component<Props, State> {
state = {error: null, fetchKey: 0};

static getDerivedStateFromError(error): State {
return {error: error, fetchKey: 0};
}

_retry = () => {
this.setState(prev => ({
// Clear the error
error: null,
// Increment and set a new fetchKey in order
// to trigger a re-evaluation and refetching
// of the query using useLazyLoadQuery
fetchKey: prev.fetchKey + 1,
}));
}

render() {
const {children, fallback} = this.props;
const {error, fetchKey} = this.state;
if (error) {
if (typeof fallback === 'function') {
return fallback({error, retry: this._retry});
}
return fallback;
}
return children({fetchKey});
}
}
  • 當發生錯誤時,我們會渲染提供的 fallback
  • 當呼叫 retry 時,我們會清除錯誤,並遞增 fetchKey,然後我們可以將其傳遞給 useLazyLoadQuery。這將會使我們使用新的 fetchKey 重新渲染使用 useLazyLoadQuery 的元件,確保在新的 useLazyLoadQuery 呼叫時重新擷取查詢。
/**
* App.react.js
*/

const ErrorBoundaryWithRetry = require('ErrorBoundaryWithRetry');
const React = require('React');

const MainContent = require('./MainContent.react');

// NOTE: This is NOT actual production code;
// it is only used to illustrate example
function App() {
return (
<ErrorBoundaryWithRetry
fallback={({error, retry}) =>
<>
<ErrorUI error={error} />
{/* Render a button to retry; this will attempt to re-render the
content inside the boundary, i.e. the query component */}
<Button onPress={retry}>Retry</Button>
</>
}>
{({fetchKey}) => {
// If we have retried, use the new `retryQueryRef` provided
// by the Error Boundary
return <MainContent fetchKey={fetchKey} />;
}}
</ErrorBoundaryWithRetry>
);
}

/**
* MainContent.react.js
*/
function MainContent(props) {
const data = useLazyLoadQuery(
graphql`...`,
variables,
{fetchKey: props.fetchKey}
);

return (/* ... */);
}
  • 此範例程式碼中的範例錯誤邊界將會為 fallback 提供一個 retry 函式,我們可以利用它來清除錯誤,並使用新的 fetchKey 重新渲染 useLazyLoadQuery。這將會導致重新評估並重新擷取查詢,且 useLazyLoadQuery 會啟動新的網路請求並暫停。

存取 GraphQL 回應中的錯誤

如果您希望存取應用程式中的錯誤資訊以顯示對使用者友善的訊息,建議的方法是將錯誤資訊建模並公開為 GraphQL 架構的一部分。

例如,您可以在架構中公開一個欄位,該欄位會傳回預期的結果,或是在解析該欄位時發生錯誤時,傳回錯誤物件(而不是傳回 null)

type Error {
# User friendly message
message: String!
}

type Foo {
bar: Result | Error
}