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 モニタリングでグラフィカル インターフェイスに中国語の文字化けが発生する問題を解決する方法

推薦する

Vueはユーザーログイン切り替えを実装します

この記事では、ユーザーのログイン切り替えを実現するためのVueの具体的なコードを例として紹介します。...

初心者向けの一般的な Linux システムコマンドの完全なリスト

Linux コマンドの学習は、ほとんどの初心者にとって最大の障害です。今日は、Linux システムで...

WeChatアプレットでグローバル変数を監視する方法

最近、仕事で問題に遭遇しました。グローバル変数 red_heart があります。これは多くの場所で使...

jQueryはキャンバスタグを使用して検証コードを描画します

<canvas> 要素は、クライアント側のベクター グラフィックス用に設計されています。...

複数のネットワークカードを備えた Linux システムでのルーティング構成の詳細な説明

Linux でのルーティング設定コマンド1. ホストルーティングを追加する ルートを追加 -host...

Vue で配列をクリアするいくつかの方法 (要約)

目次1. はじめに2. データを消去するいくつかの方法2.1 ref() の使用2.2 スライスの使...

MySQL クイックデータ比較テクニック

MySQL の運用と保守において、R&D の同僚が 2 つの異なるインスタンスのデータを比較...

Nexus を使用して Docker リポジトリを作成する方法

公式の Docker レジストリを使用して作成されたウェアハウスでは、イメージを削除してもデフォルト...

Win32 MySQL 5.7.27 のインストールと設定方法のグラフィックチュートリアル

MySQL 5.7.27のインストールチュートリアルは以下のように記録され、皆さんと共有されています...

MySQL トランザクション分離レベルの表示と変更の例

トランザクション分離レベルを確認するMySQL では、'%tx_isolation%'...

MySQL 5.7 JSON 型の使用の詳細

JSON は、言語に依存しないテキスト形式を使用する軽量のデータ交換形式で、XML に似ていますが、...

インデックスとテーブルリターンをカバーするMySQLの使い方

インデックスの2つの主要なカテゴリ使用されるストレージエンジン: MySQL 5.7 InnoDBク...

ES6のシンボルデータ型について詳しく説明します

目次シンボルデータタイプシンボルが表示される理由シンボルの特徴シンボルの応用rbオブジェクトにupメ...

MySQL 8.0.22 winx64 のインストールと設定方法のグラフィックチュートリアル

MySQL-8.0.22-winx64のデータベースインストールチュートリアルは参考になります。具体...

Vueモバイル端末は画面上で指をスライドさせる方向を判定する

vueモバイル端末は、画面上で指をスライドさせる方向を判断します。具体的な内容は次のとおりです。これ...