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. 更新自带pip

    想安装docker-compose发现居然找不到pip curl https://bootstrap.pypa.io/get-pip.py | python 直接sudo不行.还是提示权限不够. su ...

  2. angular编译机制

    转载https://segmentfault.com/a/1190000011562077 Angular编译机制 前言 http://www.cnblogs.com/ztwBlog/p/620975 ...

  3. scss 覆盖 原有变量

    在scss的variables.scss 中会有很多的变量 $color: red !default; $body-color: $color !default; 这些!default 和我们理解的! ...

  4. Python 网络编程和Socket

    2017-07-24 20:43:49 Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求.Http协议主要的操作流程是req ...

  5. LeetCode--141--环形链表

    问题描述: 给定一个链表,判断链表中是否有环. 思路:用快的指针追慢的指针,只要有圈,一定能追上. 错误: class Solution(object): def hasCycle(self, hea ...

  6. 20170612xlVBA含方框文档填表

    Sub mainProc() Application.ScreenUpdating = False Application.DisplayAlerts = wdAlertsNone 'Dim xlAp ...

  7. ssh The authenticity of host '10.11.26.2 (10.11.26.2)' can't be established

    The authenticity of host '10.11.26.2 (10.11.26.2)' can't be established. ECDSA key fingerprint is SH ...

  8. M爷的线段树

    M爷的线段树 - BUCTOJ 3305 一个长度为n的数列A.修改m次,每次给区间[L,R]中的每一个数加X.查询k次,每次查询第i个元素的值并输出.1<=n<=1e5 ,1<=m ...

  9. EBS 中iSupplier模块中的MAPPING_ID

    在EBS的供应商模块中,有一个非常有意思的表 POS_SUPPLIER_MAPPINGS, 这个表中建立了supplier_reg_id,vendor_id,party_id之间的映射关系. 这个表中 ...

  10. MyBatis:4

    转载:http://www.cnblogs.com/xrq730/p/5289638.html 什么是动态SQL MyBatis的一个强大特性之一通常是它的动态SQL能力.如果你有使用JDBC或其他相 ...