参考答案:
EerrorBoundary
是16版本出来的,之前的 15 版本有unstable_handleError
。
关于 ErrorBoundary
官网介绍比较详细,它能捕捉以下异常:
1class ErrorBoundary extends React.Component { 2 constructor(props) { 3 super(props); 4 this.state = { hasError: false }; 5 } 6 7 componentDidCatch(error, info) { 8 // Display fallback UI 9 this.setState({ hasError: true }); 10 // You can also log the error to an error reporting service 11 logErrorToMyService(error, info); 12 } 13 14 render() { 15 if (this.state.hasError) { 16 // You can render any custom fallback UI 17 return <h1>Something went wrong.</h1>; 18 } 19 return this.props.children; 20 } 21} 22 23 24<ErrorBoundary> 25 <MyWidget /> 26</ErrorBoundary>
可以考虑直接使用开源库:react-error-boundary,对开发者来说,只需要关心出现错误后的处理。
1import {ErrorBoundary} from 'react-error-boundary' 2 3function ErrorFallback({error, resetErrorBoundary}) { 4 return ( 5 <div role="alert"> 6 <p>Something went wrong:</p> 7 <pre>{error.message}</pre> 8 <button onClick={resetErrorBoundary}>Try again</button> 9 </div> 10 ) 11} 12 13const ui = ( 14 <ErrorBoundary 15 FallbackComponent={ErrorFallback} 16 onReset={() => { 17 // reset the state of your app so the error doesn't happen again 18 }} 19 > 20 <ComponentThatMayError /> 21 </ErrorBoundary> 22)
遗憾的是,error boundaries
并不会捕捉这些错误:
原文可见参见官网introducing-error-boundaries
其实官方也有解决方案:how-about-event-handlers, 就是 try catch.
1 handleClick() { 2 try { 3 // Do something that could throw 4 } catch (error) { 5 this.setState({ error }); 6 } 7 }
我们先看看一张表格,罗列了我们能捕获异常的手段和范围。
异常类型 | 同步方法 | 异步方法 | 资源加载 | Promise | async/await |
---|---|---|---|---|---|
try/catch | √ | √ | |||
window.onerror | √ | √ | |||
error | √ | √ | √ | ||
unhandledrejection | √ | √ |
可以捕获同步和async/await的异常。
1 window.addEventListener('error', this.onError, true); 2 window.onerror = this.onError
window.addEventListener('error')
这种可以比 window.onerror
多捕获资源记载异常.
请注意最后一个参数是 true
, false
的话可能就不如你期望。
当然你如果问题这第三个参数的含义,我就有点不想理你了。拜。
请注意最后一个参数是 true
。
1window.removeEventListener('unhandledrejection', this.onReject, true)
其捕获未被捕获的Promise的异常。
XMLHttpRequest
很好处理,自己有onerror事件。
当然你99.99%也不会自己基于XMLHttpRequest
封装一个库, axios
真香,有这完毕的错误处理机制。
至于fetch
, 自己带着catch跑,不处理就是你自己的问题了。
其实有一个库 react-error-catch 是基于ErrorBoudary,error与unhandledrejection封装的一个组件。
其核心如下
1 ErrorBoundary.prototype.componentDidMount = function () { 2 // event catch 3 window.addEventListener('error', this.catchError, true); 4 // async code 5 window.addEventListener('unhandledrejection', this.catchRejectEvent, true); 6 };
使用:
1import ErrorCatch from 'react-error-catch' 2 3const App = () => { 4 return ( 5 <ErrorCatch 6 app="react-catch" 7 user="cxyuns" 8 delay={5000} 9 max={1} 10 filters={[]} 11 onCatch={(errors) => { 12 console.log('报错咯'); 13 // 上报异常信息到后端,动态创建标签方式 14 new Image().src = `http://localhost:3000/log/report?info=${JSON.stringify(errors)}` 15 }} 16 > 17 <Main /> 18 </ErrorCatch>) 19} 20 21export default
利用error捕获的错误,其最主要的是提供了错误堆栈信息,对于分析错误相当不友好,尤其打包之后。
使用decorator来重写原来的方法。
先看一下使用:
1 2 @methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) 3 async createOrder() { 4 const data = {...}; 5 const res = await createOrder(); 6 if (!res || res.errCode !== 0) { 7 return Toast.error("创建订单失败"); 8 } 9 10 ....... 11 其他可能产生异常的代码 12 ....... 13 14 Toast.success("创建订单成功"); 15 }
注意四个参数:
再看一段代码
1 @methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) 2 async createOrder() { 3 const data = {...}; 4 const res = await createOrder(); 5 if (!res || res.errCode !== 0) { 6 return Toast.error("创建订单失败"); 7 } 8 9 ....... 10 其他可能产生异常的代码 11 ....... 12 13 throw new CatchError("创建订单失败了,请联系管理员", { 14 toast: true, 15 report: true, 16 log: false 17 }) 18 19 Toast.success("创建订单成功"); 20 21 }
是都,没错,你可以通过抛出 自定义的CatchError
来覆盖之前的默认选项。
这个methodCatch
可以捕获,同步和异步的错误,我们来一起看看全部的代码。
1export interface CatchOptions { 2 report?: boolean; 3 message?: string; 4 log?: boolean; 5 toast?: boolean; 6} 7 8// 这里写到 const.ts更合理 9export const DEFAULT_ERROR_CATCH_OPTIONS: CatchOptions = { 10 report: true, 11 message: "未知异常", 12 log: true, 13 toast: false 14}
1import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch"; 2 3export class CatchError extends Error { 4 5 public __type__ = "__CATCH_ERROR__"; 6 /** 7 * 捕捉到的错误 8 * @param message 消息 9 * @options 其他参数 10 */ 11 constructor(message: string, public options: CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) { 12 super(message); 13 } 14} 15
1import Toast from "@components/Toast"; 2import { CatchOptions, DEFAULT_ERROR_CATCH_OPTIONS } from "@typess/errorCatch"; 3import { CatchError } from "@util/error/CatchError"; 4 5 6const W_TYPES = ["string", "object"]; 7export function methodCatch(options: string | CatchOptions = DEFAULT_ERROR_CATCH_OPTIONS) { 8 9 const type = typeof options; 10 11 let opt: CatchOptions; 12 13 14 if (options == null || !W_TYPES.includes(type)) { // null 或者 不是字符串或者对象 15 opt = DEFAULT_ERROR_CATCH_OPTIONS; 16 } else if (typeof options === "string") { // 字符串 17 opt = { 18 ...DEFAULT_ERROR_CATCH_OPTIONS, 19 message: options || DEFAULT_ERROR_CATCH_OPTIONS.message, 20 } 21 } else { // 有效的对象 22 opt = { ...DEFAULT_ERROR_CATCH_OPTIONS, ...options } 23 } 24 25 return function (_target: any, _name: string, descriptor: PropertyDescriptor): any { 26 27 const oldFn = descriptor.value; 28 29 Object.defineProperty(descriptor, "value", { 30 get() { 31 async function proxy(...args: any[]) { 32 try { 33 const res = await oldFn.apply(this, args); 34 return res; 35 } catch (err) { 36 // if (err instanceof CatchError) { 37 if(err.__type__ == "__CATCH_ERROR__"){ 38 err = err as CatchError; 39 const mOpt = { ...opt, ...(err.options || {}) }; 40 41 if (mOpt.log) { 42 console.error("asyncMethodCatch:", mOpt.message || err.message , err); 43 } 44 45 if (mOpt.report) { 46 // TODO:: 47 } 48 49 if (mOpt.toast) { 50 Toast.error(mOpt.message); 51 } 52 53 } else { 54 55 const message = err.message || opt.message; 56 console.error("asyncMethodCatch:", message, err); 57 58 if (opt.toast) { 59 Toast.error(message); 60 } 61 } 62 } 63 } 64 proxy._bound = true; 65 return proxy; 66 } 67 }) 68 return descriptor; 69 } 70}
1 @methodCatch({ message: "创建订单失败", toast: true, report:true, log:true }) 2 async createOrder() { 3 const data = {...}; 4 const res = await createOrder(); 5 if (!res || res.errCode !== 0) { 6 return Toast.error("创建订单失败"); 7 } 8 Toast.success("创建订单成功"); 9 10 ....... 11 其他可能产生异常的代码 12 ....... 13 14 throw new CatchError("创建订单失败了,请联系管理员", { 15 toast: true, 16 report: true, 17 log: false 18 }) 19 }
1 2@XXXCatch 3classs AAA{ 4 @YYYCatch 5 method = ()=> { 6 } 7}
当前方案存在的问题:
之后,我们会围绕着这些问题,继续展开。
Hook的名字就叫useCatch
1 2const TestView: React.FC<Props> = function (props) { 3 4 const [count, setCount] = useState(0); 5 6 7 const doSomething = useCatch(async function(){ 8 console.log("doSomething: begin"); 9 throw new CatchError("doSomething error") 10 console.log("doSomething: end"); 11 }, [], { 12 toast: true 13 }) 14 15 const onClick = useCatch(async (ev) => { 16 console.log(ev.target); 17 setCount(count + 1); 18 19 doSomething(); 20 21 const d = delay(3000, () => { 22 setCount(count => count + 1); 23 console.log() 24 }); 25 console.log("delay begin:", Date.now()) 26 27 await d.run(); 28 29 console.log("delay end:", Date.now()) 30 console.log("TestView", this) 31 throw new CatchError("自定义的异常,你知道不") 32 }, 33 [count], 34 { 35 message: "I am so sorry", 36 toast: true 37 }); 38 39 return <div> 40 <div><button onClick={onClick}>点我</button></div> 41 <div>{count}</div> 42 </div> 43} 44 45export default React.memo(TestView);
至于思路,基于useMemo
,可以先看一下代码:
1export function useCatch<T extends (...args: any[]) => any>(callback: T, deps: DependencyList, options: CatchOptions =DEFAULT_ERRPR_CATCH_OPTIONS): T { 2 3 const opt = useMemo( ()=> getOptions(options), [options]); 4 5 const fn = useMemo((..._args: any[]) => { 6 const proxy = observerHandler(callback, undefined, function (error: Error) { 7 commonErrorHandler(error, opt) 8 }); 9 return proxy; 10 11 }, [callback, deps, opt]) as T; 12 13 return fn; 14} 15
最近更新时间:2024-08-10