React中如何优雅的捕捉事件错误
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.其他
- http请求
 封装后,专门处理
- 状态管理redux,mobx等
 封装拦截 ,我们在项目的应用mobx-state-tree基本都是在model里面拦截的
- 其他
 自己看着办啊,都找我,我很忙的。
问题
啊?这么多事件处理和方法都要加try catch啊。 你笨啊window.onerror啊。
onerror是非常好,但是有个问题,错误细节不好分析,有大神说,正则解析。
我不扶墙扶你。
解决
decorator特性,装饰器。 create-react-app创建的app默认是不知此的装饰器的。
不要和我争,github地址上人家写着呢can-i-use-decorators?
那问题又来了,如何支持装饰器。
- 场景一:自己构建的项目
 那还不简单的飞起
- 场景二: create-react-app脚手架创建的项目
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;
    }
}
关于装饰器这里不做过多的说明,修改类的行为。
这里又有几个点
- 装饰方法 装饰类 装饰getter, setter都可以,我们选在装饰方法和类
- 装饰类,如何排除系统内置方法和继承的方法
- 装饰的时候有参和无参数怎么处理
我们先写一个来检查内置方法的方法, 不够自己补全
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中如何优雅的捕捉事件错误的更多相关文章
- react中使用map时onClick事件失效
		分享一些踩过的坑 react中使用map时onClick事件失效 <span> { count.map( (item,index)=>{ return <span style= ... 
- React应该如何优雅的绑定事件?
		前言 由于JS的灵活性,我们在React中其实有很多种绑定事件的方式,然而,其实有许多我们常见的事件绑定,其实并不是高效的.所以本文想给大家介绍一下React绑定事件的正确姿势. 常见两种种错误绑定事 ... 
- react中实现原生enter/回车事件及antdesign组件实现方式
		先直接上核心代码: this.goToHomePage换成自己逻辑 自己写的时候直接把this.goToHmoPage()换成自己的逻辑就行了,还有注意一点的是: 需要传个空函数,不然会报错 在com ... 
- 【原】React中,map出来的元素添加事件无法使用
		在使用react中,经常用到react的map函数,用法和jquery里中的map一样,但是,如果你在每个map出来的元素中添加,你会发觉添加的事件无法关联, 比如,我们很多的评论,我需要在每个评论下 ... 
- React 中阻止事件冒泡的问题
		在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行. JS 中事件的监听与处理 事件捕获与冒泡 DOM 事件会先后经历 捕获 与 冒泡 两个阶段.捕获即事件沿着 DOM 树由上往下传递,到达 ... 
- React中的响应式设计思想和事件绑定
		这两个点是react入门非常重要的两个点,以前我们是直接操作dom的形式去做,react的设计思想和以前直接操作dom是完全不同的,react是一个响应式的框架,他在做编程的时候,强调的是我们不要直接 ... 
- react 中 EventEmitter 事件总线机制
		此机制可用于 react 中兄弟组件中的通信 npm install events -S 事件总线: // eventBus.js import {EventEmitter} from 'events ... 
- 如何优雅地在React中处理事件响应&&React绑定onClick为什么要用箭头函数?
		React绑定onClick为什么要用箭头函数? https://segmentfault.com/q/1010000010918131 如何优雅地在React中处理事件响应 https://segm ... 
- 六、React 键盘事件 表单事件 事件对象以及React中的ref获取dom节点 、React实现类似Vue的双向数据绑定
		接:https://www.cnblogs.com/chenxi188/p/11782349.html 事件对象 .键盘事件. 表单事件 .ref获取dom节点.React实现类似vue双向数据绑定 ... 
随机推荐
- TypeScript 小记
			1. 对比JavaScript TypeScript是JavaScript的超集,可编译为JavaScript,主要提供类型系统等增强代码的可读性和可维护性,适合中大型项目多人协作: TypeScri ... 
- Python mysql-表中数据的大量插入
			2017-09-06 23:28:26 import pymysql db = pymysql.connect("localhost","root"," ... 
- C#使用xpath查找xml节点信息
			Xpath是功能很强大的,但是也是相对比较复杂的一门技术,最好还是到博客园上面去专门找一些专业的帖子来看一看,下面是一些简单的Xpath语法和一个实例,提供给你参考一下. xml示例: <?xm ... 
- centos7的FTP服务vsftpd里建立虚拟用户不同目录分配不同权限
			1. virtual_use_local_privs参数 当virtual_use_local_privs=YES时,虚拟用户和本地用户有相同的权限: 当virtual_use_local_privs ... 
- English trip -- VC(情景课)1 D
			Read 阅读 Welcome! Meet our new student. His first name is Ernesto. 欧内斯托 His last name is Delgado. 德尔 ... 
- Lightoj Halloween Costumes
			题意:给出要n个时间穿的服装.服装脱下就不能再穿.问最少要准备多少? dp[i][j]表示i到j之间最少花费.如果n=1(n指长度),肯定结果为1,n=2时,也很好算.然后n=3的时候dp[i][j] ... 
- H.Playing games
			fwt #include<bits/stdc++.h> using namespace std; const int N=1<<19; const int mod=100000 ... 
- eclipse properties 文件查看和编辑插件 Properties Editor
			Properties Edito官网地址:http://propedit.sourceforge.jp/index_en.html Properties Edito安装地址:http://proped ... 
- dubbo为consumer创建代理
			ReferenceConfig.init()方法中获取到了最终的代理对象,先观察一下代理对象的视图. 默认使用javassist生成动态类,可配置proxy为jdk,则使用jdk动态代理: <d ... 
- response.setHeader的各种用法 ------ 笔记(一)
			转载地址:https://blog.csdn.net/junmoxi/article/details/76976692 1.一秒刷新页面一次 response.setHeader("refr ... 
