Reactで例外を適切にキャプチャする方法

Reactで例外を適切にキャプチャする方法

序文

完璧な人間はいないのですから、コードには常にエラーが存在します。エラーはひどいものではありません。重要なのは、エラーにどう対処するかです。
React アプリケーションでエラーをキャプチャする方法を教えてください。 現時点では:

  • Xiaobai+++: どう対処すればいいですか?
  • 小百++: エラー境界
  • Xiaobai+: ErrorBoundary、try catch
  • Xiaohei#: ErrorBoundary、try catch、window.onerror
  • Xiaohei##: これは深刻な問題です。対処法はたくさんあります。もっと良い解決策はありますか?

エラー境界

EerrorBoundary はバージョン 16 で登場しました。誰かが私にバージョン 15 はどうなのかと尋ねました。私はそれを聞きたくありませんでした。とにかく私はバージョン 16 を使っていますし、もちろん 15 にはunstable_handleErrorがあります。

ErrorBoundary の公式サイトには詳しい紹介がありますが、これは重要ではありません。重要なのは、どのような例外をキャプチャできるかです。

  • 子コンポーネントのレンダリング
  • ライフサイクル関数
  • コンストラクタ
  • ErrorBoundaryクラスはReact.Componentを拡張します。
  コンストラクタ(props) {
    スーパー(小道具);
    this.state = {hasError: false };
  }

  コンポーネントDidCatch(エラー、情報) {
    // フォールバックUIを表示する
    this.setState({hasError: true });
    // エラーをエラー報告サービスに記録することもできます
    logErrorToMyService(エラー、情報);
  }

  与える() {
    if (this.state.hasError) {
      // カスタムフォールバックUIをレンダリングできます
      <h1>問題が発生しました。</h1> を返します。
    }
    this.props.children を返します。
  }
}

<エラー境界>
  <マイウィジェット />
</エラー境界>

オープンソースの世界は素晴らしいです。偉大な作者がすでに react-error-boundary のような優れたライブラリをカプセル化しています。
エラーが発生した後に何をするかだけを考えて、リセットするだけで完璧です。

'react-error-boundary' から {ErrorBoundary} をインポートします。

関数 ErrorFallback({error, resetErrorBoundary}) {
  戻る (
    <div ロール="アラート">
      <p>問題が発生しました:</p>
      <pre>{エラーメッセージ}</pre>
      <button onClick={resetErrorBoundary}>もう一度お試しください</button>
    </div>
  )
}

定数ui = (
  <エラー境界
    フォールバックコンポーネント = {ErrorFallback}
    onReset={() => {
      // エラーが再発しないようにアプリの状態をリセットします
    }}
  >
    <エラーが発生する可能性のあるコンポーネント />
  </エラー境界>
)

残念ながら、エラー境界では次のエラーは検出されません。

  • イベントハンドラ
  • 非同期コード (例: setTimeout または requestAnimationFrame コールバック)
  • サーバー側レンダリングコード
  • エラー境界 自身によってスローされるエラー

原文は公式ウェブサイトintroducing-error-boundariesでご覧いただけます。

この記事の目的は、イベント ハンドラーのエラーをキャプチャすることです。
公式の解決策は、how-about-event-handlers、つまり try catch です。

しかし、イベント ハンドラーがこんなにたくさんあるなんて、いったいいくつ書かなければならないのでしょうか? 。 。 。 。 。 。 。 。 。 。 。 。 。 。 。 。 。 。 。

  ハンドルクリック() {
    試す {
      // 何かを投げる可能性がある
    } キャッチ(エラー){
      this.setState({エラー});
    }
  }

エラー境界を超えて

まず、例外をキャプチャできる手段と範囲を示す表を見てみましょう。

例外タイプ同期メソッド非同期メソッドリソースの読み込み約束非同期/待機
トライ/キャッチ
ウィンドウ.onerror
エラー
未処理の拒否

トライ/キャッチ

同期例外と async/await 例外の両方をキャッチできます。

window.onerror、エラーイベント

    window.addEventListener('error', this.onError, true);
    window.onerror = this.onError

window.addEventListener('error') は、window.onerror よりも多くのリソースをキャプチャし、例外を記録できます。
最後のパラメータは true であることに注意してください。false の場合は期待どおりに動作しない可能性があります。
もちろん、3 番目のパラメータの意味について尋ねられた場合、私はあなたと話したくありません。さよなら。

未処理の拒否

最後のパラメータが true であることに注意してください。

window.removeEventListener('unhandledrejection', this.onReject, true)

キャッチされていない Promise 例外をキャッチします。

XMLHttpRequestとフェッチ

XMLHttpRequest は扱いやすく、独自の onerror イベントを備えています。
もちろん、皆さんの 99.99% は、XMLHttpRequest に基づくライブラリを自分でカプセル化することはないでしょう。Axios は、完全なエラー処理メカニズムを備えているので、本当に優れています。
フェッチに関しては、自分で catch を実行しても処理しない場合は、それは自分の問題になります。
多すぎて難しい。
幸いなことに、ErrorBoudary、error、および unhandledrejection のカプセル化に基づくコンポーネントであるライブラリ react-error-catch が実際に存在します。
核心は次のとおりです

   ErrorBoundary.prototype.componentDidMount = 関数 () {
        // イベントキャッチ
        window.addEventListener('error', this.catchError, true);
        // 非同期コード
        window.addEventListener('unhandledrejection', this.catchRejectEvent, true);
    };

使用:

'react-error-catch' から ErrorCatch をインポートします。

定数App = () => {
  戻る (
  <エラーキャッチ
      アプリ="react-catch"
      ユーザー="cxyuns"
      遅延={5000}
      最大={1}
      フィルター={[]}
      onCatch={(エラー) => {
        console.log('エラーが報告されました');
        // 例外情報をバックエンドに報告し、動的にタグを作成します。new Image().src = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}`
      }}
    >
      <メイン />
    </ErrorCatch>)
}

エクスポートデフォルト

拍手、拍手。
実際はそうではありません。error によってキャプチャされたエラーの最も重要な点は、エラー スタック情報を提供することですが、これは特にパッケージ化後のエラー分析には非常に不便です。
エラーが非常に多いため、まずはReactのイベントハンドラーを扱います。
残りについては、また次回に続きます。

イベントハンドラでの例外キャッチ


私のアイデアは非常にシンプルで、デコレータを使用して元のメソッドを書き換えます。
まずは使い方を見てみましょう:

   @methodCatch({ message: "注文の作成に失敗しました", toast: true, report:true, log:true })
    非同期createOrder() {
        定数データ = {...};
        const res = createOrder() を待機します。
        (!res || res.errCode !== 0)の場合{
            Toast.error("注文の作成に失敗しました"); を返します。
        }
        
        .......
        例外を引き起こす可能性のあるその他のコード...
        
       Toast.success("注文が正常に作成されました");
    }

次の 4 つのパラメータに注意してください。

  • メッセージ: エラーが発生すると、エラーが印刷されます
  • トースト: エラーが発生しました。トーストするかどうか
  • 報告: エラーが発生した場合に報告するかどうか
  • ログ: console.errorを使用して印刷します

おそらく、これは確実かつ不合理なニュースだと言うでしょう。他に何かニュースがあったらどうしますか?
このとき私は笑って言いました。「心配しないでください。別のコードを見てみましょう。」

  @methodCatch({ message: "注文の作成に失敗しました", toast: true, report:true, log:true })
    非同期createOrder() {
        定数データ = {...};
        const res = createOrder() を待機します。
        (!res || res.errCode !== 0)の場合{
            Toast.error("注文の作成に失敗しました"); を返します。
        }
       
        .......
        例外を引き起こす可能性のあるその他のコード...
        
       throw new CatchError("注文の作成に失敗しました。管理者に連絡してください", {
           トースト:本当、
           報告: 本当、
           ログ: 偽
       })
       
       Toast.success("注文が正常に作成されました");

    }

はい、その通りです。カスタム CatchError をスローすることで、デフォルトのオプションをオーバーライドできます。
このメソッドCatchは、同期エラーと非同期エラーの両方をキャプチャできます。コード全体を見てみましょう。

タイプ定義

エクスポートインターフェースCatchOptions {
    レポート?: ブール値;
    メッセージ?: 文字列;
    ログ?: ブール値;
    トースト?: ブール値;
}

// const.ts に記述する方が合理的です export const DEFAULT_ERROR_CATCH_OPTIONS: CatchOptions = {
    報告: 本当、
    メッセージ:「不明な例外」、
    ログ: 真、
    トースト: 偽
}

カスタム CatchError

"@typess/errorCatch" から CatchOptions、DEFAULT_ERROR_CATCH_OPTIONS をインポートします。

エクスポートクラスCatchErrorはErrorを拡張します{

    パブリック __type__ = "__CATCH_ERROR__";
    /**
     * キャッチされたエラー * @param message メッセージ * @options その他のパラメータ */
    コンストラクター(メッセージ: 文字列、パブリックオプション: CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {
        super(メッセージ);
    }
}

デコレーター

"@components/Toast" から Toast をインポートします。
"@typess/errorCatch" から CatchOptions、DEFAULT_ERROR_CATCH_OPTIONS をインポートします。
"@util/error/CatchError" から CatchError をインポートします。


const W_TYPES = ["文字列", "オブジェクト"];
エクスポート関数 methodCatch(options: string | CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) {

    const type = typeof オプション;

    opt ​​: CatchOptions; を設定します。

    
    if (options == null || !W_TYPES.includes(type)) { // null または文字列またはオブジェクトではない opt ​​= DEFAULT_ERROR_CATCH_OPTIONS;
    } else if (typeof options === "string") { // 文字列 opt = {
            ...DEFAULT_ERROR_CATCH_OPTIONS、
            メッセージ: オプション || DEFAULT_ERROR_CATCH_OPTIONS.message、
        }
    } else { // 有効なオブジェクト opt ​​= { ...DEFAULT_ERROR_CATCH_OPTIONS, ...options }
    }

    戻り関数 (_target: any、_name: string、記述子: PropertyDescriptor): any {

        const oldFn = 記述子.値;

        Object.defineProperty(記述子、"値"、{
            得る() {
                非同期関数プロキシ(...引数: any[]) {
                    試す {
                        const res = await oldFn.apply(this, args);
                        res を返します。
                    } キャッチ (エラー) {
                        // if (err instanceof CatchError) {
                        if(err.__type__ == "__CATCH_ERROR__"){
                            err = err を CatchError として返します。
                            const mOpt = { ...opt, ...(err.options || {}) };

                            (mOpt.log)の場合{
                                console.error("asyncMethodCatch:", mOpt.message || err.message , err);
                            }

                            if (mOpt.report) {
                                // やるべきこと::
                            }

                            if (mOpt.toast) {
                                Toast.error(mOpt.message);
                            }

                        } それ以外 {
                            
                            定数メッセージ = err.message || opt.message;
                            console.error("asyncMethodCatch:", メッセージ, エラー);

                            if (opt.toast) {
                                Toast.error(メッセージ);
                            }
                        }
                    }
                }
                プロキシ_bound = true;
                プロキシを返します。
            }
        })
        記述子を返します。
    }
}

総括する

デコレータを使用して、エラーをキャプチャする元のメソッドを書き換えます。エラー クラスをカスタマイズしてスローし、デフォルトのオプションをオーバーライドします。柔軟性が向上しました。

  @methodCatch({ message: "注文の作成に失敗しました", toast: true, report:true, log:true })
    非同期createOrder() {
        定数データ = {...};
        const res = createOrder() を待機します。
        (!res || res.errCode !== 0)の場合{
            Toast.error("注文の作成に失敗しました"); を返します。
        }
       Toast.success("注文が正常に作成されました");
       
        .......
        例外を引き起こす可能性のあるその他のコード...
        
       throw new CatchError("注文の作成に失敗しました。管理者に連絡してください", {
           トースト:本当、
           報告: 本当、
           ログ: 偽
       })
    }

次のステップ

次のステップは何でしょうか? 一歩ずつ進んでいきましょう。
いいえ、道のりはまだ長いです。 これは単なる基本バージョンです。

結果の拡大

フォロー
クラスAAA{
    フォロー
    メソッド = () => {
    }
}

抽象的、抽象的、抽象的

さようなら。

最後に

エラー境界
React例外処理
反応エラーをキャッチする
React の高度な例外処理メカニズム - エラー境界
デコレータ
コアデコレータ
自動バインド

React で例外をエレガントに捕捉する方法についての記事はこれで終わりです。React で例外を捕捉する方法についての詳細は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • 最も単純な ErrorBoundary コンポーネントをカプセル化して、React 例外を処理する
  • React 16における例外処理の詳細な説明

<<:  MySQLデータベース移行により、大量のデータを迅速にエクスポートおよびインポートできます

>>:  PHP の問題により、Zabbix モニタリングでグラフィカル インターフェイスに中国語の文字化けが発生する問題を解決する方法

推薦する

HTML で margin:0 auto を使用するとページ全体が中央に配置されない問題の解決方法

今日、jsp ページを書きました。<div style="margin:0 auto...

node.js グローバル変数の具体的な使用法

グローバルオブジェクトすべてのモジュールは呼び出すことができますglobal: ブラウザの wind...

Windows での PyTorch 開発環境のインストール チュートリアル

アナコンダのインストールAnaconda は、Python の使用を容易にするために作成されたソフト...

ウェブページの読み込み速度を上げる6つのヒント

第二に、キーワードのランキングは、Webページの表示速度にも関係しています(参照:キーワードランキン...

MySQLトリガーの使用

トリガーにより、ステートメントの実行前または実行後に他の SQL コードを実行できます。トリガーは、...

Linux での MySQL 5.6.33 のインストールと設定のチュートリアル

このチュートリアルでは、LinuxでのMySQL 5.6.33のインストールと設定方法を参考までに紹...

Linux システムで .sh ファイルを実行する方法

Linux システムで .sh ファイルを実行する方法は 2 つあります。たとえば、ルート ディレク...

JavaScriptカルーセルの実装について

今日もとても実践的な事例です。名前を聞くだけで高度で難しそうですよね?今日はカルーセル画像の真髄を簡...

Javascriptの基礎を学ぶための10の重要な質問

目次1. Javascript とは何ですか? 2. DOMとは何か3. JSコードの実行方法4. ...

Centos6でgitlabを構築する方法

序文元のプロジェクトは、パブリックネットワークgitlabに配置されていました。セキュリティ上の理由...

nginx をベースにリロードなしでアップストリーム サーバーの動的な自動起動と停止を実装する方法

目次1. Consulクラスタをデプロイする1. 準備3. Consulクラスタを作成する4. 管理...

Jenkins Docker 静的エージェント ノードのビルド プロセス

静的ノードはマシン上に固定されており、いくつかの固定コマンドを通じて起動されます。動的ノードには複数...

CSSをiPhoneのフルスクリーンに適応させる方法

1. メディアクエリ方式 /*iPhone X への適応*/ @media 画面のみ、(デバイス幅:...

SHTML 簡潔なチュートリアル

SHTMLとASPは似ています。SHTMLという名前のファイルでは、SSIの命令がASPの命令と同じ...

WeChatアプレットが検索ボックス機能を実装

この記事の例では、WeChatアプレットの検索ボックス機能を実装するための具体的なコードを参考までに...