誤差境界と反応するエラーの捕捉


著者によってKristofer Selbekk
最も完璧なアプリケーションでさえ、時々実行時エラーがあります.ネットワークがタイムアウトするかもしれません、いくつかのバックエンドサービスが下がるかもしれません、あるいは、あなたのユーザはちょうど計算しないいくつかの入力をあなたに提供するかもしれません.または-あなたは-バグを知っている.しかし、あなたのアプリが弾力性があることを保証するためにエラーを処理する最良の方法は何です、応答して、最高のユーザーエクスペリエンスを提供し続けますか?
この記事では、反応のエラー境界の概念を紹介します.我々は、彼らが解決しようとする課題、彼らが持っている欠点、およびそれらを実装する方法を見ていきます.最後に、我々はエラー境界をより良いものにする小さい抽象化層を見ます!

エラー境界とは何か?


エラー境界は、アプリケーションのエラーを処理する方法です.それはあなたが反応し、実行可能なエラーからだけでなく、該当する場合、フォールバックユーザーインターフェイスを提供するように回復することができます.
エラー境界の背後にあるアイデアは、アプリケーションの一部を特殊なコンポーネントで包むことができます-いわゆるエラー境界-そして、アプリケーションのその部分が捕捉されていないエラーを発生した場合、そのコンポーネント内に格納されます.その後、エラーを表示することができますお好みのエラー報告サービスに報告し、可能な限り回復しようとします.
エラー境界は反応16で導入されて、反応チームのFiber rewrite effort . クラスコンポーネントとして書く必要がある唯一のコンポーネントです.しかし、間違いなくどんな現代的な反応アプリケーションの一部である必要があります.
アプリケーションを通していくつかのエラー境界を作成することができますが、多くのアプリケーションでは、ルートレベルで1つを選択する傾向があります.あなたが望むならば、あなたは超粒状に行くことができます、しかし、私の経験はルートレベル1がしばしば十分であると私に言います.

最初のエラー境界


エラー境界は、以下のメソッドの1つまたは両方を実装する正規クラスのコンポーネントです.

静的getDeriveStateFromError ( Error )


このメソッドは、エラーに基づいて新しい状態を返します.通常、フォールバックユーザーインターフェイスを提供するかどうかをエラー境界に示す状態フラグを反転します.

コンポーネントdidcatch (エラー、エラー情報)


このメソッドはエラーが発生するたびに呼び出されます.エラー(と余分な情報)をお好みのエラー報告サービスにログを、エラーから回復しようとすると、他に何かを行う必要があります.
これをどのように実装するかを示すために、一歩一歩を作りましょう.まず、正規クラスのコンポーネントを作成しましょう.
class ErrorBoundary extends React.Component {
    render() {
    return this.props.children;
    }  
}
このコンポーネントはあまり機能しません.エラーをエラーサービスに記録しましょう!
class ErrorBoundary extends React.Component {
    componentDidCatch(error, errorInfo) {
        errorService.log({ error, errorInfo });
    }
    render() {
        return this.props.children;
    }  
}
今、我々のユーザーのためにエラーが発生するたびに、我々はエラー報告サービスを通して通知されます.エラー自体が発生し、エラーが発生したコンポーネント全体のスタックが表示されます.これは、我々のバグを後で仕事を修理することを大いに簡単にするつもりです!
しかし、我々はまだアプリケーションを破壊している!それはたいしたことではない.フォールバック"Oops "UIを提供しましょう.そのためには、我々が誤った状態にあるかどうかを追跡する必要がありますgetDerivedStateFromError 方法が来る!
class ErrorBoundary extends React.Component {
    state = { hasError: false };
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    componentDidCatch(error, errorInfo) {
        errorService.log({ error, errorInfo });
    }
    render() {
        if (this.state.hasError) {
            return <h1>Oops, we done goofed up</h1>;
        }
        return this.props.children;
    }  
}
そして今、我々は基本的な、まだ機能的なエラー境界を持っている!

使用開始


さあ、使い始めましょう.単にあなたのルートアプリケーションコンポーネントをラップしますErrorBoundary 成分!
ReactDOM.render(
    <ErrorBoundary>
        <App />
    </ErrorBoundary>,
    document.getElementById('root')
)
いくつかの基本的なレイアウト(ヘッダー、フッターなど)も同様にエラー境界を配置したいかもしれません.

リセット機能を追加!


時々、このようなエラーはUIがいくつかのファンキーな状態になったときに起こります.エラーが発生するたびに、エラー境界のサブツリー全体がアンマウントされます.“もう一度試してみたい”ボタンを使用すると、新鮮な状態でサブツリーを再マウントしようとすると、すぐに良いアイデアをすることができますユーザーを提供する!そのことをしましょう.
class ErrorBoundary extends React.Component {
    state = { hasError: false };
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    componentDidCatch(error, errorInfo) {
        errorService.log({ error, errorInfo });
    }
    render() {
        if (this.state.hasError) {
            return (
            <div>
                <h1>Oops, we done goofed up</h1>
                <button type="button" onClick={() => this.setState({ hasError: false })}>
                Try again?
                </button>
            </div>
            );
        }
        return this.props.children;
    }  
}
もちろん、これはあなたのアプリケーションにとって良い考えではないかもしれません.このような機能を実装する際には、独自のニーズやユーザを考慮してください.

オープンソースセッション


Webアプリケーションの生産におけるデバッグは、挑戦的で、時間がかかるかもしれません.OpenReplay フルストーリー、ログオンとhotjarにオープンソースの代替手段です.それはあなたが監視し、すべてのユーザーが再生し、どのようにすべての問題のためにあなたのアプリの行動を示す再生することができます.
それはあなたのユーザーの肩を見ながら、ブラウザの検査官を開いているようなものだ.
OpenReplayは現在利用可能なオープンソースの代替手段です.

ハッピーデバッギング、現代のフロントエンドチームStart monitoring your web app for free .

制限


エラー境界は、彼らがすることに最適です-あなたがレンダリングの間、予想されなかったランタイムエラーを捕えてください.しかし、キャッチされていないいくつかの種類のエラーがあり、別の方法で対処する必要があります.以下を含みます:
  • イベントハンドラのエラー
  • 非同期コールバックのエラー(例: settimeout )
  • エラー境界コンポーネント自体で発生するエラー
  • サーバー側レンダリング中に発生するエラー
  • これらの制限は厳しいと思われるかもしれませんが、ほとんどの時間はtry catchと類似したhasError 状態.
    function SignUpButton(props) {
        const [hasError, setError] = React.useState(false);
        const handleClick = async () => {
            try {
                await api.signUp();
            } catch(error) {
                errorService.log({ error })
                setError(true);
            }
        }
        if (hasError) {
            return <p>Sign up failed!</p>;
        }
        return <button onClick={handleClick}>Sign up</button>;
    }
    
    あなたがコードのいくつかの行を複製しなければならないとしても、これは十分に働きます.

    より良いエラー境界の作成


    エラー境界はデフォルトでよいです、しかし、同様に彼らのエラー処理論理をイベントハンドラと非同期の場所に再利用するのはすばらしいです.それはコンテキストAPIを実装するのに十分簡単です!
    エラー境界スーパーパワーを与えるために、手動でエラーを引き起こす関数を実装しましょう.
    class ErrorBoundary extends React.Component {
        state = { hasError: false };
        static getDerivedStateFromError(error) {
            return { hasError: true };
        }
        componentDidCatch(error, errorInfo) {
            errorService.log({ error, errorInfo });
        }
        triggerError = ({ error, errorInfo }) => {
            errorService.log({ error, errorInfo });
            this.setState({ hasError: true });
        }
        resetError = () => this.setState({ hasError: false });
        render() {
            if (this.state.hasError) {
                return <h1>Oops, we done goofed up</h1>;
            }
            return this.props.children;
        }  
    }
    
    次に、コンテキストを作成し、新しい関数を渡します.
    const ErrorBoundaryContext = React.createContext(() => {});
    
    次に、子要素からエラートリガ機能を取得するためのカスタムフックを作成できます.
    const useErrorHandling = () => {
        return React.useContext(ErrorBoundaryContext)
    }
    
    次に、エラーコンテキストをこのコンテキストでラップしましょう.
    class ErrorBoundary extends React.Component {
        state = { hasError: false };
        static getDerivedStateFromError(error) {
            return { hasError: true };
        }
        componentDidCatch(error, errorInfo) {
            errorService.log({ error, errorInfo });
        }
        triggerError = ({ error, errorInfo }) => {
            errorService.log({ error, errorInfo });
            this.setState({ hasError: true });
        }
        resetError = () => this.setState({ hasError: false });
        render() {
            return (
                <ErrorBoundaryContext.Provider value={this.triggerError}>
                {this.state.hasError 
                    ? <h1>Oops, we done goofed up</h1>
                    : this.props.children
                }
                </ErrorBoundaryContext.Provider>
            );
        }  
    }
    
    今、我々は同様に我々のイベントハンドラからエラーを引き起こすことができます!
    function SignUpButton(props) {
        const { triggerError } = useErrorHandling();
        const handleClick = async () => {
            try {
                await api.signUp();
            } catch(error) {
                triggerError(error);
            }
        }
        return <button onClick={handleClick}>Sign up</button>;
    }
    
    今、我々はエラー報告について考えるか、我々が実装するすべてのクリック・ハンドラのためのフォールバックUIを作成する必要はありません.

    反応誤差境界の利用


    上記のように独自のエラー境界ロジックを書くことはすばらしいですし、ほとんどのユースケースを扱うべきです.だがこれが解決問題だ.コアチームメンバーブライアンボーン(そして、後で、非常に才能のある反応教師ケントC .ドッド)を反応させる[react-error-boundary](https://www.npmjs.com/package/react-error-boundary) npm package それはあなたに上記とほとんど同じことを与えます.
    APIは少し異なっているので、カスタムのフォールバックコンポーネントを渡すことができますし、独自の書き込みの代わりにロジックをリセットするが、それは非常に同様の方法で使用されます.以下に例を示します:
    ReactDOM.render(
        <ErrorBoundary 
            FallbackComponent={MyFallbackComponent}
            onError={(error, errorInfo) => errorService.log({ error, errorInfo })}
        >
            <App />
        </ErrorBoundary>,
        document.getElementById('root')
    )
    
    また、見ることができますhow it’s implemented - これはフックからのエラーをトリガする別のアプローチを使用しますが、それ以外の場合はかなり同じように動作します.

    概要


    エラーと予期しないイベントのハンドリングは、どんな品質のアプリケーションにとっても重要です.物事が計画通りに行かないときでも、素晴らしいユーザーエクスペリエンスを提供することは非常に重要です.
    エラー境界はあなたのアプリケーションを優雅に失敗させて、残りがあなたのアプリケーションの部分に誤りさえ含む間、残りが働き続けている素晴らしい方法です!自分自身を書いてくださいreact-error-boundary あなたのためにそれをするライブラリ.何を選択しても、あなたのユーザーはあなたに感謝します!