react 的事件机制(React如何优雅的捕获异常)
react 的事件机制
React如何优雅的捕获异常目录
- 前言
- ErrorBoundary
- Error Boundary 之外
- try/catch
- window.onerror , error事件
- unhandledrejection
- XMLHttpRequest 与 fetch
- 事件处理程序的异常捕获
- 示例
- 类型定义
- 自定义的CatchError
- 装饰器
- 总结一下
- 下一步
- 写在最后
人无完人,所以代码总会出错,出错并不可怕,关键是怎么处理。
我就想问问大家react的应用的错误怎么捕捉呢? 这个时候:
- 小白+++:怎么处理?
- 小白++: ErrorBoundary
- 小白+: ErrorBoundary, try catch
- 小黑#: ErrorBoundary, try catch, window.onerror
- 小黑##: 这个是个严肃的问题,我知道N种处理方式,你有什么更好的方案?
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 <h1>Something went wrong.</h1>; } return this.props.children; } } <ErrorBoundary> <MyWidget /> </ErrorBoundary>
开源世界就是好,早有大神封装了react-error-boundary 这种优秀的库。
你只需要关心出现错误后需要关心什么,还以来个 Reset, 完美。
import {ErrorBoundary} from 'react-error-boundary' function ErrorFallback({error, resetErrorBoundary}) { return ( <li role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </li> ) } 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)
- 服务端的渲染代码
- error boundaries自己抛出的错误
原文可见参见官网introducing-error-boundaries
本文要捕获的就是 事件处理程序的错误。
官方其实也是有方案的how-about-event-handlers, 就是 try catch.
但是,那么多事件处理程序,我的天,得写多少,。。。。。。。。。。。。。。。。。。。。
handleClick() { try { // Do something that could throw } catch (error) { this.setState({ error }); } }
我们先看看一张表格,罗列了我们能捕获异常的手段和范围。
异常类型
同步方法
异步方法
资源加载
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 = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}` }} > <Main /> </ErrorCatch>) } 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("创建订单失败"); } ....... 其他可能产生异常的代码 ....... Toast.success("创建订单成功"); }
注意四个参数:
- 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 = ["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; } }
利用装饰器重写原方法,达到捕获错误的目的
自定义错误类,抛出它,就能达到覆盖默认选项的目的。增加了灵活性。
@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 = ()=> { } }
抽象,再抽象,再抽象
再见。
error-boundaries
React异常处理
catching-react-errors
react进阶之异常处理机制-error Boundaries
decorator
core-decorators
autobind.js
到此这篇关于React如何优雅的捕获异常的文章就介绍到这了,更多相关React 捕获异常内容请搜索开心学习网以前的文章或继续浏览下面的相关文章希望大家以后多多支持开心学习网!
- react新手指引页面编写(React+TypeScript进行项目构建案例讲解)
- react组件封装成函数方法(React虚拟列表的实现)
- react自适应布局如何实现(React实现分页效果)
- react怎么绑定state(react纯函数组件setState更新页面不刷新的解决)
- react子组件的动态参数(浅谈React Component生命周期函数)
- react定时任务(手把手带你用React撸一个日程组件)
- reactnative ios(详解React Native与IOS端之间的交互)
- reactnative动态设置值(react native实现监控手势上下拉动效果)
- react app框架(浅谈React原生APP更新)
- react组件参数(浅析React中的受控组件和非受控组件)
- react怎样实现响应式计算属性(深入浅析React中diff算法)
- react重点和难点(关于React状态管理的三个规则总结)
- react源码教程(详解React 代码共享最佳实践方式)
- react怎么添加动态html(react中的DOM操作实现)
- react配置上下文路由(React中Portals与错误边界处理实现)
- react循环有几种方法(React forwardRef的使用方法及注意点)
- 电影《民间怪谈录之走阴人》定档8月5日,开启一场中式惊悚之旅(电影民间怪谈录之走阴人定档8月5日)
- 原创图画书,以儿童视角讲述中国故事(以儿童视角讲述中国故事)
- 八月再见 愿你岁月不扰,余生静好(八月再见愿你岁月不扰)
- 赏读 八月再见,九月你好(赏读八月再见九月你好)
- 散文 八月再见,九月,我在风中等你(散文八月再见九月)
- 8月再见 9月你好(8月再见)
热门推荐
- 阿里云服务器内外网址(阿里云服务器网站发现后门该怎么处理)
- 如何启动所有docker服务(docker 使用CMD或者ENTRYPOINT命令同时启动多个服务)
- python简单代码画图(python+pyqt5实现图片批量缩放工具)
- apache搭建ftp服务器(使用Apache&花生壳架设Web服务器)
- matlab嵌入python代码(python调用matlab的m自定义函数方法)
- showModalDialog在谷歌浏览器下获取不到返回值
- python使用教程操作(详解python中@的用法)
- css怎么让div整体居中(CSS实现DIV居中的三种方法)
- mysql返回结果集函数(mysql 判断是否为子集的方法步骤)
- css设置图片透明度
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9