2021-06-17React,优雅的捕获异

 

 

React,优雅的捕获异常 

 React,优雅的捕获异常 此文转载乐字节

前言 人无完人,所以代码总会出错,出错并不可怕,关键是怎么处理。 我就想问问大家react的应用的错误怎么捕捉呢? 这个时候:

小白+++:怎么处理? 小白++: ErrorBoundary 小白+: ErrorBoundary, try catch 小黑#: ErrorBoundary, try catch, window.onerror 小黑##: 这个是个严肃的问题,我知道N种处理方式,你有什么更好的方案? ErrorBoundary EerrorBoundary是16版本出来的,有人问那我的15版本呢,我不听我不听,反正我用16,当然15有unstable_handleError。

关于ErrorBoundary官网介绍比较详细,这个不是重点,重点是他能捕捉哪些异常。

子组件的渲染 生命周期函数 构造函数 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

Something went wrong. ; } return this.props.children; } } 复制代码 开源世界就是好,早有大神封装了react-error-boundary 这种优秀的库。 你只需要关心出现错误后需要关心什么,还以来个 Reset, 完美。 import {ErrorBoundary} from 'react-error-boundary'

function ErrorFallback({error, resetErrorBoundary}) { return (

Something went wrong:

{error.message} Try again ) } const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => { // reset the state of your app so the error doesn't happen again }}

复制代码 ) 复制代码 遗憾的是,error boundaries并不会捕捉这些错误: 事件处理程序 异步代码 (e.g. setTimeout or requestAnimationFrame callbacks) 服务端的渲染代码 error boundaries自己抛出的错误 原文可见参见官网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 多捕获资源记载异常. 请注意最后一个参数是 true, false的话可能就不如你期望。

当然你如果问题这第三个参数的含义,我就有点不想理你了。拜。

unhandledrejection 请注意最后一个参数是 true。

window.removeEventListener('unhandledrejection', this.onReject, true) 复制代码 其捕获未被捕获的Promise的异常。

XMLHttpRequest 与 fetch XMLHttpRequest 很好处理,自己有onerror事件。 当然你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('报错咯'); // 上报异常信息到后端,动态创建标签方式 new Image().src =

) } export default 复制代码 鼓掌,鼓掌。

其实不然: 利用error捕获的错误,其最主要的是提供了错误堆栈信息,对于分析错误相当不友好,尤其打包之后。

错误那么多,我就先好好处理React里面的事件处理程序。 至于其他,待续。

事件处理程序的异常捕获 示例 我的思路原理很简单,使用decorator来重写原来的方法。

先看一下使用:

@methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("创建订单失败"); }

.......复制代码

} 复制代码 复制代码 注意四个参数:

message: 出现错误时,打印的错误 toast: 出现错误,是否Toast report: 出现错误,是否上报 log: 使用使用console.error打印 可能你说,这这,消息定死,不合理啊。我要是有其他消息呢。 此时我微微一笑别急, 再看一段代码

@methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("创建订单失败"); }

....... 其他可能产生异常的代码 .......复制代码

throw new CatchError("创建订单失败了,请联系管理员", { toast: true, report: true, log: false })

Toast.success("创建订单成功");

} 复制代码 复制代码 是都,没错,你可以通过抛出 自定义的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 其他参数

*/ 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 = 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;复制代码

} 复制代码 } 复制代码 总结一下 利用装饰器重写原方法,达到捕获错误的目的 自定义错误类,抛出它,就能达到覆盖默认选项的目的。增加了灵活性。 @methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) async createOrder() { const data = {...}; const res = await createOrder(); if (!res || res.errCode !== 0) { return Toast.error("创建订单失败"); } Toast.success("创建订单成功");

....... 其他可能产生异常的代码 .......复制代码

throw new CatchError("创建订单失败了,请联系管理员", { toast: true, report: true, log: false }) } 复制代码 复制代码 下一步 啥下一步,走一步看一步啦。

不,接下来的路,还很长。 这才是一个基础版本。

扩大成果

@XXXCatch classs AAA{ @YYYCatch method = ()=> { } } 复制代码 抽象,再抽象,再抽象 再见。

ps需自学视频关注B站:——免费领取

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值