React如何優(yōu)雅的捕獲異常
前言
人無(wú)完人,所以代碼總會(huì)出錯(cuò),出錯(cuò)并不可怕,關(guān)鍵是怎么處理。
我就想問(wèn)問(wèn)大家react的應(yīng)用的錯(cuò)誤怎么捕捉呢? 這個(gè)時(shí)候:
- 小白+++:怎么處理?
- 小白++: ErrorBoundary
- 小白+: ErrorBoundary, try catch
- 小黑#: ErrorBoundary, try catch, window.onerror
- 小黑##: 這個(gè)是個(gè)嚴(yán)肅的問(wèn)題,我知道N種處理方式,你有什么更好的方案?
ErrorBoundary
EerrorBoundary是16版本出來(lái)的,有人問(wèn)那我的15版本呢,我不聽(tīng)我不聽(tīng),反正我用16,當(dāng)然15有unstable_handleError
。
關(guān)于ErrorBoundary官網(wǎng)介紹比較詳細(xì),這個(gè)不是重點(diǎn),重點(diǎn)是他能捕捉哪些異常。
- 子組件的渲染
- 生命周期函數(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>
開(kāi)源世界就是好,早有大神封裝了react-error-boundary 這種優(yōu)秀的庫(kù)。
你只需要關(guān)心出現(xiàn)錯(cuò)誤后需要關(guān)心什么,還以來(lái)個(gè) 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并不會(huì)捕捉這些錯(cuò)誤:
- 事件處理程序
- 異步代碼 (e.g. setTimeout or requestAnimationFrame callbacks)
- 服務(wù)端的渲染代碼
- error boundaries自己拋出的錯(cuò)誤
原文可見(jiàn)參見(jiàn)官網(wǎng)introducing-error-boundaries
本文要捕獲的就是 事件處理程序的錯(cuò)誤。
官方其實(shí)也是有方案的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 多捕獲資源記載異常.
請(qǐng)注意最后一個(gè)參數(shù)是 true, false的話可能就不如你期望。
當(dāng)然你如果問(wèn)題這第三個(gè)參數(shù)的含義,我就有點(diǎn)不想理你了。拜。
unhandledrejection
請(qǐng)注意最后一個(gè)參數(shù)是 true。
window.removeEventListener('unhandledrejection', this.onReject, true)
其捕獲未被捕獲的Promise的異常。
XMLHttpRequest 與 fetch
XMLHttpRequest 很好處理,自己有onerror事件。
當(dāng)然你99.99%也不會(huì)自己基于XMLHttpRequest封裝一個(gè)庫(kù), axios 真香,有這完畢的錯(cuò)誤處理機(jī)制。
至于fetch, 自己帶著catch跑,不處理就是你自己的問(wèn)題了。
這么多,太難了。
還好,其實(shí)有一個(gè)庫(kù)react-error-catch 是基于ErrorBoudary,error與unhandledrejection封裝的一個(gè)組件。
其核心如下
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('報(bào)錯(cuò)咯'); // 上報(bào)異常信息到后端,動(dòng)態(tài)創(chuàng)建標(biāo)簽方式 new Image().src = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}` }} > <Main /> </ErrorCatch>) } export default
鼓掌,鼓掌。
其實(shí)不然: 利用error捕獲的錯(cuò)誤,其最主要的是提供了錯(cuò)誤堆棧信息,對(duì)于分析錯(cuò)誤相當(dāng)不友好,尤其打包之后。
錯(cuò)誤那么多,我就先好好處理React里面的事件處理程序。
至于其他,待續(xù)。
事件處理程序的異常捕獲
示例
我的思路原理很簡(jiǎn)單,使用decorator來(lái)重寫原來(lái)的方法。
先看一下使用:
@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)建訂單成功"); }
注意四個(gè)參數(shù):
- message: 出現(xiàn)錯(cuò)誤時(shí),打印的錯(cuò)誤
- toast: 出現(xiàn)錯(cuò)誤,是否Toast
- report: 出現(xiàn)錯(cuò)誤,是否上報(bào)
- log: 使用使用console.error打印
可能你說(shuō),這這,消息定死,不合理啊。我要是有其他消息呢。
此時(shí)我微微一笑別急, 再看一段代碼
@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)建訂單失敗了,請(qǐng)聯(lián)系管理員", { toast: true, report: true, log: false }) Toast.success("創(chuàng)建訂單成功"); }
是都,沒(méi)錯(cuò),你可以通過(guò)拋出 自定義的CatchError來(lái)覆蓋之前的默認(rèn)選項(xiàng)。
這個(gè)methodCatch可以捕獲,同步和異步的錯(cuò)誤,我們來(lái)一起看看全部的代碼。
類型定義
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__"; /** * 捕捉到的錯(cuò)誤 * @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 或者 不是字符串或者對(duì)象 opt = DEFAULT_ERROR_CATCH_OPTIONS; } else if (typeof options === "string") { // 字符串 opt = { ...DEFAULT_ERROR_CATCH_OPTIONS, message: options || DEFAULT_ERROR_CATCH_OPTIONS.message, } } else { // 有效的對(duì)象 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é)一下
利用裝飾器重寫原方法,達(dá)到捕獲錯(cuò)誤的目的
自定義錯(cuò)誤類,拋出它,就能達(dá)到覆蓋默認(rèn)選項(xiàng)的目的。增加了靈活性。
@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)建訂單失敗了,請(qǐng)聯(lián)系管理員", { toast: true, report: true, log: false }) }
下一步
啥下一步,走一步看一步啦。
不,接下來(lái)的路,還很長(zhǎng)。 這才是一個(gè)基礎(chǔ)版本。
擴(kuò)大成果
@XXXCatch classs AAA{ @YYYCatch method = ()=> { } }
抽象,再抽象,再抽象
再見(jiàn)。
寫在最后
error-boundaries
React異常處理
catching-react-errors
react進(jìn)階之異常處理機(jī)制-error Boundaries
decorator
core-decorators
autobind.js
到此這篇關(guān)于React如何優(yōu)雅的捕獲異常的文章就介紹到這了,更多相關(guān)React 捕獲異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React-Router如何進(jìn)行頁(yè)面權(quán)限管理的方法
本篇文章主要介紹了React-Router如何進(jìn)行頁(yè)面權(quán)限管理的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12react 權(quán)限樹形結(jié)構(gòu)實(shí)現(xiàn)代碼
這篇文章主要介紹了react 權(quán)限樹形結(jié)構(gòu)實(shí)現(xiàn)代碼,項(xiàng)目背景react + ant design,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-05-05詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案
這篇文章主要介紹了詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03