React如何優(yōu)雅的捕獲異常
前言
人無完人,所以代碼總會出錯,出錯并不可怕,關(guān)鍵是怎么處理。
我就想問問大家react的應(yīng)用的錯誤怎么捕捉呢? 這個時候:
- 小白+++:怎么處理?
- 小白++: ErrorBoundary
- 小白+: ErrorBoundary, try catch
- 小黑#: ErrorBoundary, try catch, window.onerror
- 小黑##: 這個是個嚴肅的問題,我知道N種處理方式,你有什么更好的方案?
ErrorBoundary
EerrorBoundary是16版本出來的,有人問那我的15版本呢,我不聽我不聽,反正我用16,當(dāng)然15有unstable_handleError
。
關(guān)于ErrorBoundary官網(wǎng)介紹比較詳細,這個不是重點,重點是他能捕捉哪些異常。
- 子組件的渲染
- 生命周期函數(shù)
- 構(gòu)造函數(shù)
- class ErrorBoundary extends React.Component {
constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } } <ErrorBoundary> <MyWidget /> </ErrorBoundary>
開源世界就是好,早有大神封裝了react-error-boundary 這種優(yōu)秀的庫。
你只需要關(guān)心出現(xiàn)錯誤后需要關(guān)心什么,還以來個 Reset, 完美。
import {ErrorBoundary} from 'react-error-boundary' function ErrorFallback({error, resetErrorBoundary}) { return ( <div role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ) } const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { // reset the state of your app so the error doesn't happen again }} > <ComponentThatMayError /> </ErrorBoundary> )
遺憾的是,error boundaries并不會捕捉這些錯誤:
- 事件處理程序
- 異步代碼 (e.g. setTimeout or requestAnimationFrame callbacks)
- 服務(wù)端的渲染代碼
- error boundaries自己拋出的錯誤
原文可見參見官網(wǎng)introducing-error-boundaries
本文要捕獲的就是 事件處理程序的錯誤。
官方其實也是有方案的how-about-event-handlers, 就是 try catch.
但是,那么多事件處理程序,我的天,得寫多少,。。。。。。。。。。。。。。。。。。。。
handleClick() { try { // Do something that could throw } catch (error) { this.setState({ error }); } }
Error Boundary 之外
我們先看看一張表格,羅列了我們能捕獲異常的手段和范圍。
異常類型 | 同步方法 | 異步方法 | 資源加載 | Promise | async/await |
---|---|---|---|---|---|
try/catch | √ | √ | |||
window.onerror | √ | √ | |||
error | √ | √ | √ | ||
unhandledrejection | √ | √ |
try/catch
可以捕獲同步和async/await的異常。
window.onerror , error事件
window.addEventListener('error', this.onError, true); window.onerror = this.onError
window.addEventListener('error') 這種可以比 window.onerror 多捕獲資源記載異常.
請注意最后一個參數(shù)是 true, false的話可能就不如你期望。
當(dāng)然你如果問題這第三個參數(shù)的含義,我就有點不想理你了。拜。
unhandledrejection
請注意最后一個參數(shù)是 true。
window.removeEventListener('unhandledrejection', this.onReject, true)
其捕獲未被捕獲的Promise的異常。
XMLHttpRequest 與 fetch
XMLHttpRequest 很好處理,自己有onerror事件。
當(dāng)然你99.99%也不會自己基于XMLHttpRequest封裝一個庫, axios 真香,有這完畢的錯誤處理機制。
至于fetch, 自己帶著catch跑,不處理就是你自己的問題了。
這么多,太難了。
還好,其實有一個庫react-error-catch 是基于ErrorBoudary,error與unhandledrejection封裝的一個組件。
其核心如下
ErrorBoundary.prototype.componentDidMount = function () { // event catch window.addEventListener('error', this.catchError, true); // async code window.addEventListener('unhandledrejection', this.catchRejectEvent, true); };
使用:
import ErrorCatch from 'react-error-catch' const App = () => { return ( <ErrorCatch app="react-catch" user="cxyuns" delay={5000} max={1} filters={[]} onCatch={(errors) => { console.log('報錯咯'); // 上報異常信息到后端,動態(tài)創(chuàng)建標簽方式 new Image().src = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}` }} > <Main /> </ErrorCatch>) } export default
鼓掌,鼓掌。
其實不然: 利用error捕獲的錯誤,其最主要的是提供了錯誤堆棧信息,對于分析錯誤相當(dāng)不友好,尤其打包之后。
錯誤那么多,我就先好好處理React里面的事件處理程序。
至于其他,待續(xù)。
事件處理程序的異常捕獲
示例
我的思路原理很簡單,使用decorator來重寫原來的方法。
先看一下使用:
@methodCatch({ message: "創(chuàng)建訂單失敗", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("創(chuàng)建訂單失敗"); } ....... 其他可能產(chǎn)生異常的代碼 ....... Toast.success("創(chuàng)建訂單成功"); }
注意四個參數(shù):
- message: 出現(xiàn)錯誤時,打印的錯誤
- toast: 出現(xiàn)錯誤,是否Toast
- report: 出現(xiàn)錯誤,是否上報
- log: 使用使用console.error打印
可能你說,這這,消息定死,不合理啊。我要是有其他消息呢。
此時我微微一笑別急, 再看一段代碼
@methodCatch({ message: "創(chuàng)建訂單失敗", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("創(chuàng)建訂單失敗"); } ....... 其他可能產(chǎn)生異常的代碼 ....... throw new CatchError("創(chuàng)建訂單失敗了,請聯(lián)系管理員", { toast: true, report: true, log: false }) Toast.success("創(chuàng)建訂單成功"); }
是都,沒錯,你可以通過拋出 自定義的CatchError來覆蓋之前的默認選項。
這個methodCatch可以捕獲,同步和異步的錯誤,我們來一起看看全部的代碼。
類型定義
export interface CatchOptions { report?: boolean; message?: string; log?: boolean; toast?: boolean; } // 這里寫到 const.ts更合理 export const DEFAULT_ERROR_CATCH_OPTIONS: CatchOptions = { report: true, message: "未知異常", log: true, toast: false }
自定義的CatchError
import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch"; export class CatchError extends Error { public __type__ = "__CATCH_ERROR__"; /** * 捕捉到的錯誤 * @param message 消息 * @options 其他參數(shù) */ constructor(message: string, public options: CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) { super(message); } }
裝飾器
import Toast from "@components/Toast"; import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch"; import { CatchError } from "@util/error/CatchError"; const W_TYPES = ["string", "object"]; export function methodCatch(options: string | CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) { const type = typeof options; let 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, message: options || DEFAULT_ERROR_CATCH_OPTIONS.message, } } else { // 有效的對象 opt = { ...DEFAULT_ERROR_CATCH_OPTIONS, ...options } } return function (_target: any, _name: string, descriptor: PropertyDescriptor): any { const oldFn = descriptor.value; Object.defineProperty(descriptor, "value", { get() { async function proxy(...args: any[]) { try { const res = await oldFn.apply(this, args); return res; } catch (err) { // if (err instanceof CatchError) { if(err.__type__ == "__CATCH_ERROR__"){ err = err as CatchError; const mOpt = { ...opt, ...(err.options || {}) }; if (mOpt.log) { console.error("asyncMethodCatch:", mOpt.message || err.message , err); } if (mOpt.report) { // TODO:: } if (mOpt.toast) { Toast.error(mOpt.message); } } else { const message = err.message || opt.message; console.error("asyncMethodCatch:", message, err); if (opt.toast) { Toast.error(message); } } } } proxy._bound = true; return proxy; } }) return descriptor; } }
總結(jié)一下
利用裝飾器重寫原方法,達到捕獲錯誤的目的
自定義錯誤類,拋出它,就能達到覆蓋默認選項的目的。增加了靈活性。
@methodCatch({ message: "創(chuàng)建訂單失敗", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("創(chuàng)建訂單失敗"); } Toast.success("創(chuàng)建訂單成功"); ....... 其他可能產(chǎn)生異常的代碼 ....... throw new CatchError("創(chuàng)建訂單失敗了,請聯(lián)系管理員", { toast: true, report: true, log: false }) }
下一步
啥下一步,走一步看一步啦。
不,接下來的路,還很長。 這才是一個基礎(chǔ)版本。
擴大成果
@XXXCatch classs AAA{ @YYYCatch method = ()=> { } }
抽象,再抽象,再抽象
再見。
寫在最后
error-boundaries
React異常處理
catching-react-errors
react進階之異常處理機制-error Boundaries
decorator
core-decorators
autobind.js
到此這篇關(guān)于React如何優(yōu)雅的捕獲異常的文章就介紹到這了,更多相關(guān)React 捕獲異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React-Router如何進行頁面權(quán)限管理的方法
本篇文章主要介紹了React-Router如何進行頁面權(quán)限管理的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12react 權(quán)限樹形結(jié)構(gòu)實現(xiàn)代碼
這篇文章主要介紹了react 權(quán)限樹形結(jié)構(gòu)實現(xiàn)代碼,項目背景react + ant design,本文結(jié)合實例代碼給大家介紹的非常詳細,感興趣的朋友一起看看吧2024-05-05詳解基于React.js和Node.js的SSR實現(xiàn)方案
這篇文章主要介紹了詳解基于React.js和Node.js的SSR實現(xiàn)方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03