前话
人无完人, 所以代码总会出错, 出错并不可怕, 关键是怎么处理.
我就想问问大家 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? https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#can-i-use-decorators
那问题又来了, 如何支持装饰器.
场景一: 自己构建的项目
那还不简单的飞起
场景二: create-react-app 脚手架创建的项目
- npm run eject https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject
- https://github.com/timarney/react-app-rewired
- 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
- }
再弄一个装饰方法的方法, 这个方法参考了 https://github.com/jayphelps/core-decorators/blob/master/src/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 里面可以传递参数么, 可以提供额外的错误信息, 比如场景, 是不是致命错误等等信息.
她解决了你未显示处理的事件处理错误, 有没有很优雅, 有没有.
你们都说没有的话, 我就放弃前端了, 可是我还有老婆孩子要养, 所以你们一定要有人说有.
https://reactjs.org/docs/error-boundaries.html
React 异常处理 https://www.colabug.com/1867349.html
https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/
react 进阶之异常处理机制 - error Boundaries https://blog.csdn.net/a986597353/article/details/78469979
- http://es6.ruanyifeng.com/#docs/decorator
- https://github.com/jayphelps/core-decorators
- https://github.com/jayphelps/core-decorators/blob/master/src/autobind.js
来源: https://www.cnblogs.com/cloud-/p/9366234.html