React中如何优雅的捕捉事件错误

前话

人无完人,所以代码总会出错,出错并不可怕,关键是怎么处理。

我就想问问大家react的错误怎么捕捉呢? 这个时候:

  • 小白:怎么处理?
  • 小白+: ErrorBoundary
  • 小白++: ErrorBoundary, try catch
  • 小白#: ErrorBoundary, try catch, window.onerror
  • 小白##: 这个是个严肃的问题,我知道*种处理方式,你有什么好的方案?

正题

小白#回答的基本就是解决思路。我们来一个一个简单说说。

1. EerrorBoundary

EerrorBoundary是16版本出来的,有人问那我的15版本呢,我不听我不听,反正我用16,当然15有unstable_handleError。

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

Error boundaries在rendering,lifeCyclemethod或处于他们树层级之下的构造函数中捕获错误

哦,原来如此。 怎么用

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>

重点:error boundaries并不会捕捉这些错误:

  • 事件处理器
  • 异步代码
  • 服务端的渲染代码
  • 在error boundaries区域内的错误

2. try catch

简单有效的捕捉

handleClick = () => {
try {
// Do something that could throw
} catch (error) {
this.setState({ error });
}
}

3. window.onerror

超级奥特曼,只是错误信息比较不好分析。

4.其他

  1. http请求

    封装后,专门处理
  2. 状态管理redux,mobx等

    封装拦截 ,我们在项目的应用mobx-state-tree基本都是在model里面拦截的
  3. 其他

    自己看着办啊,都找我,我很忙的。

问题

啊?这么多事件处理和方法都要加try catch啊。 你笨啊window.onerror啊。

onerror是非常好,但是有个问题,错误细节不好分析,有大神说,正则解析。

我不扶墙扶你。

解决

decorator特性,装饰器。 create-react-app创建的app默认是不知此的装饰器的。

不要和我争,github地址上人家写着呢can-i-use-decorators?

那问题又来了,如何支持装饰器。

const {injectBabelPlugin} = require('react-app-rewired');

/* config-overrides.js */
module.exports = {
webpack: function override(config, env) {
// babel 7
config = injectBabelPlugin('transform-decorators-legacy',config)
// babel 6
config = injectBabelPlugin('transform-decorators',config)
return config;
}
}

关于装饰器这里不做过多的说明,修改类的行为。

这里又有几个点

  1. 装饰方法 装饰类 装饰getter, setter都可以,我们选在装饰方法和类
  2. 装饰类,如何排除系统内置方法和继承的方法
  3. 装饰的时候有参和无参数怎么处理

我们先写一个来检查内置方法的方法, 不够自己补全

const PREFIX = ['component', 'unsafe_']
const BUILTIN_METHODS = [
'constructor',
'render',
'replaceState',
'setState',
'isMounted',
'replaceState'
]
// 检查是不是内置方法
function isBuiltinMethods(name) {
if (typeof name !== 'string' || name.trim() === '') {
return false
}
// 以component或者unsafe_开头
if (PREFIX.some(prefix => name.startsWith(prefix)))) {
return true
}
// 其他内置方法
if (BUILTIN_METHODS.includes(name)) {
return true
}
return false
}

再弄一个装饰方法的方法, 这个方法参考了autobind.js

handleError是自己的错误处理函数,这里没有写出来

// 监听方法

 function createDefaultSetter(key) {
return function set(newValue) {
Object.defineProperty(this, key, {
configurable: true,
writable: true,
// IS enumerable when reassigned by the outside word
enumerable: true,
value: newValue
}); return newValue;
};
} function observerHandler(fn, callback) {
return (...args) => {
try {
fn(...args)
} catch (err) {
callback(err)
}
}
}
//方法的装饰器, params是额外的参数
function catchMethod(target, key, descriptor, ...params) { if (typeof descriptor.value !== 'function') {
return descriptor
}
const { configurable, enumerable, value: fn } = descriptor
return {
configurable,
enumerable, get() {
// Class.prototype.key lookup
// Someone accesses the property directly on the prototype on which it is
// actually defined on, i.e. Class.prototype.hasOwnProperty(key)
if (this === target) {
return fn;
} // Class.prototype.key lookup
// Someone accesses the property directly on a prototype but it was found
// up the chain, not defined directly on it
// i.e. Class.prototype.hasOwnProperty(key) == false && key in Class.prototype
if (this.constructor !== constructor && getPrototypeOf(this).constructor === constructor) {
return fn;
} const boundFn = observerHandler(fn.bind(this), err => {
handleError(err, target, key, ...params)
}) defineProperty(this, key, {
configurable: true,
writable: true,
// NOT enumerable when it's a bound method
enumerable: false,
value: boundFn
}); boundFn.bound = true
return boundFn;
},
set: createDefaultSetter(key)
};
}

再来一个装饰类的

/**
* 检查是不是需要代理
* @param {*} method
* @param {*} descriptor
*/
function shouldProxy(method, descriptor) {
return typeof descriptor.value === 'function'
&& !isBuiltinMethods(method)
&& descriptor.configurable
&& descriptor.writable
&& !descriptor.value.bound
} function catchClass(targetArg, ...params) {
// 获得所有自定义方法,未处理Symbols
const target = targetArg.prototype || targetArg
let descriptors = getOwnPropertyDescriptors(target)
for (let [method, descriptor] of Object.entries(descriptors)) {
if (shouldProxy(method, descriptor)) {
defineProperty(target, method, catchMethod(target, method, descriptors[method], ...params))
}
}
}

最后暴露一个自动识别方法和类的方法

/**
*
* 未拦截getter和setter
* 未拦截Symbols属性
*/
export default function catchError(...args) {
const lastArg = args[args.length - 1]
// 无参数方法
if (isDescriptor(lastArg)) {
return catchMethod(...args)
} else {
// 无参数class?? 需要改进
if (args.length === 1 && typeof args[0] !== 'string') {
return catchClass(...args)
}
// 有参
return (...argsList) => {
// 有参数方法
if (isDescriptor(argsList[argsList.length - 1])) {
return catchMethod(...[...argsList, ...args])
}
// 有参数class
return catchClass(...[...argsList, ...args])
}
}
}

基本成型。

怎么调用

装饰类

@catchError('HeHe')
class HeHe extends React.Component { state = {
clicked: false
} onClick(){
this.setState({
clicked:true
})
this.x.y.z.xxx
} render(){
return (
<input type="button" value="点击我" onClick={this.onClick}/>
)
} }

装饰方法

class HeHe extends React.Component {
state = {
clicked: false
} @catchError('HeHe onClick')
onClick(){
this.setState({
clicked:true
})
this.x.y.z.xxx
} render(){
return (
<input type="button" value="点击我" onClick={this.onClick}/>
)
} }

当然你还可以既装饰类又装饰方法, 这个时候方法的装饰优先于类的装饰,不会重复装饰

@catchError('HeHe')
class HeHe extends React.Component { state = {
clicked: false
} @catchError('HeHe onClick')
onClick(){
this.setState({
clicked:true
})
this.x.y.z.xxx
} onClick2(){ } render(){
return (
<React.Fragment>
<input type="button" value="点击我" onClick={this.onClick}/>
<input type="button" value="点击我2" onClick={this.onClick2}/>
</React.Fragment>
)
} }

如上,细心的人可以发现, 没有 onClick.bind(this), 是的, catchError会自动完成bind,是不是很cool。

如上,现在的所有的事件处理都会被catchError里面定义的handleError处理,怎么处理就看你自己了。

有人就问了,我要是想捕捉后还要有额外处理的了,比如来个提示框之类的。

这个就取决你的需求和怎么处理,你依旧可以在你的事件处理器try catch。

二是,你没看到@catchError里面可以传递参数么,可以提供额外的错误信息,比如场景,是不是致命错误等等信息。

她解决了你未显示处理的事件处理错误,有没有很优雅,有没有。

你们都说没有的话, 我就放弃前端了,可是我还有老婆孩子要养,所以你们一定要有人说有。

error-boundaries

React异常处理

catching-react-errors

react进阶之异常处理机制-error Boundaries

decorator

core-decorators

autobind.js

React中如何优雅的捕捉事件错误的更多相关文章

  1. react中使用map时onClick事件失效

    分享一些踩过的坑 react中使用map时onClick事件失效 <span> { count.map( (item,index)=>{ return <span style= ...

  2. React应该如何优雅的绑定事件?

    前言 由于JS的灵活性,我们在React中其实有很多种绑定事件的方式,然而,其实有许多我们常见的事件绑定,其实并不是高效的.所以本文想给大家介绍一下React绑定事件的正确姿势. 常见两种种错误绑定事 ...

  3. react中实现原生enter/回车事件及antdesign组件实现方式

    先直接上核心代码: this.goToHomePage换成自己逻辑 自己写的时候直接把this.goToHmoPage()换成自己的逻辑就行了,还有注意一点的是: 需要传个空函数,不然会报错 在com ...

  4. 【原】React中,map出来的元素添加事件无法使用

    在使用react中,经常用到react的map函数,用法和jquery里中的map一样,但是,如果你在每个map出来的元素中添加,你会发觉添加的事件无法关联, 比如,我们很多的评论,我需要在每个评论下 ...

  5. React 中阻止事件冒泡的问题

    在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达 ...

  6. React中的响应式设计思想和事件绑定

    这两个点是react入门非常重要的两个点,以前我们是直接操作dom的形式去做,react的设计思想和以前直接操作dom是完全不同的,react是一个响应式的框架,他在做编程的时候,强调的是我们不要直接 ...

  7. react 中 EventEmitter 事件总线机制

    此机制可用于 react 中兄弟组件中的通信 npm install events -S 事件总线: // eventBus.js import {EventEmitter} from 'events ...

  8. 如何优雅地在React中处理事件响应&&React绑定onClick为什么要用箭头函数?

    React绑定onClick为什么要用箭头函数? https://segmentfault.com/q/1010000010918131 如何优雅地在React中处理事件响应 https://segm ...

  9. 六、React 键盘事件 表单事件 事件对象以及React中的ref获取dom节点 、React实现类似Vue的双向数据绑定

    接:https://www.cnblogs.com/chenxi188/p/11782349.html 事件对象 .键盘事件. 表单事件 .ref获取dom节点.React实现类似vue双向数据绑定 ...

随机推荐

  1. angular 模板语法(官方文档摘录)

    https://angular.cn/guide/template-syntax {{}} 和"" 如果嵌套,{{}}里面求完值,""就是原意 <h3&g ...

  2. unity中实现静态的3D对象对其他对象的跟随

    using UnityEngine; public class FollowPosition : MonoBehaviour { public Transform targetTrans; publi ...

  3. 关于网站的SYN_RECV(SYN_RECEIVED)***的防范措施

    关于网站的SYN_RECV(SYN_RECEIVED)***的防范措施 一.总结 一句话总结:SYN ***是最常见又最容易被利用的一种***手法.相信很多人还记得2000年YAHOO网站遭受的*** ...

  4. JQuery.Ajax()的data参数传递方式

    最近,新学c# mvc,通过ajax post方式传递数据到controller.刚开始传递参数,controller中总是为null.现记录一下,可能不全,纯粹记个学习日记. 重点在于参数的方式,代 ...

  5. 『PyTorch』第十三弹_torch.nn.init参数初始化

    初始化参数的方法 nn.Module模块对于参数进行了内置的较为合理的初始化方式,当我们使用nn.Parameter时,初始化就很重要,而且我们也可以指定代替内置初始化的方式对nn.Module模块进 ...

  6. CSS——标准盒子模型

    在写网页的时候一般都先用Div把网页的框架搭好(用不同的背景颜色来区分不同的Div块),然后填充每一个Div,最后把每个Div的背景颜色去掉 <html> <head> < ...

  7. Oracle 使用GSON库解析复杂json串

    在前文中讲到了如何使用JSON标准库解析json串,参考: Oracle解析复杂json的方法(转) 现补充一篇使用GSON库在Oracle中解析复杂json的方法. GSON串的使用教程参考官方文档 ...

  8. Linux,du、df统计磁盘情况不一致

    转载:http://blog.linezing.com/?p=2136 在运维Linux服务器时,会碰到需要查看硬盘空间的情况,这时候,通常会使用df -lh命令来检查每个挂载了文件系统的硬盘的总量和 ...

  9. 15年-ICPC长春-网络赛

    ID name status one word  POJ 5437 Alisha’s Party 赛后AC. 优先队列,模拟.对时间t排序 POJ 5438 Ponds 赛后AC 循环链表 POJ 5 ...

  10. HDU 1934 特殊数字

    有两种车牌号.让你判断第二种是不是在第一种之后且在第一种出Kth之前的车牌号. 本解中是把前面的字母看成一位十进制的数.自己是一个26或者21进制的数.如果比较时有两种.那么第一种和第一种的最后一个比 ...