React中如何优雅的捕捉事件错误
React中如何优雅的捕捉事件错误前话人无完人,所以代码总会出错,出错并不可怕,关键是怎么处理。
正题小白#回答的基本就是解决思路。我们来一个一个简单说说。 1. EerrorBoundaryEerrorBoundary是16版本出来的,有人问那我的15版本呢,我不听我不听,反正我用16,当然15有unstable_handleError。 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并不会捕捉这些错误:
2. try catch简单有效的捕捉 handleClick = () => { try { // Do something that could throw } catch (error) { this.setState({ error }); } } 3. window.onerror超级奥特曼,只是错误信息比较不好分析。 4.其他
问题啊?这么多事件处理和方法都要加try catch啊。 你笨啊window.onerror啊。 解决decorator特性,装饰器。 create-react-app创建的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; } } 关于装饰器这里不做过多的说明,修改类的行为。
我们先写一个来检查内置方法的方法,不够自己补全 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 // 监听方法 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,descriptor,...params) { if (typeof descriptor.value !== ‘function‘) { return descriptor } const { configurable,enumerable,value: fn } = descriptor return { configurable,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,...params) }) defineProperty(this,{ configurable: 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,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处理,怎么处理就看你自己了。 她解决了你未显示处理的事件处理错误,有没有很优雅,有没有。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |