原文地址:github.com/HuJiaoHJ/bl…react
React Native页面出现错误时:git
一、开发模式下,会出现红色背景的页面,展现当前代码错误信息github
二、bundle模式下,则会出现白屏或者闪退react-native
开发模式promise
bundle模式app
在生产环境下,因RN页面异常致使整个APP白屏或者闪退,用户体验并很差,因此应该对异常进行捕获并处理,提升用户体验异步
主要使用两种方法对RN页面的异常进行捕获并处理:ide
一、React Error Boundaries (异常边界组件)函数
二、React Native ErrorUtils 模块oop
React Error Boundaries (异常边界组件)是React 16 引入的新概念,为了不React的组件内的UI异常致使整个应用的异常
对React的异常边界组件不熟悉的小伙伴能够看看个人文章:从源码看React异常处理
这里简单介绍下:
Error Boundaries(异常边界)是React组件,用于捕获它子组件树种全部组件产生的js异常,并渲染指定的兜底UI来替代出问题的组件
它能捕获子组件生命周期函数中的异常,包括构造函数(constructor)和render函数
而不能捕获如下异常:
因此能够经过异常边界组件捕获组件生命周期内的全部异常并渲染兜底UI,防止APP白屏或闪退,提升用户体验,也可在兜底UI中指引用户反馈截图反馈问题,方便问题的排查和修复
直接上代码:
...
function withErrorBoundary(
WrappedComponent: React.ComponentType <CatchCompProps> ,
errorCallback: Function,
allowedInDevMode: boolean,
opt: Object = {}) {
return class extends React.Component <CatchCompProps, CatchCompState> {
state = {
error: null,
errorInfo: false,
visible: false,
}
componentDidCatch(error: Error, errorInfo: any) {
this.setState({
error,
errorInfo,
visible: true,
})
errorCallback && errorCallback(error, errorInfo)
}
handleLeft = () => {
...
}
render() {
const { title = 'Unexpected error occurred', message = 'Unexpected error occurred' } = opt
return (
this.state.visible && (allowedInDevMode ? true : process.env.NODE_ENV !== 'development') ? (
<Modal
visible
transparent
animationType={'fade'}>
<View style={styles.container}>
<View style={styles.header}>
<NavBar
title={title}
leftIcon={'arrow-left'}
handleLeft={this.handleLeft}/>
</View>
<View style={styles.info}>
<Text>{message}</Text>
</View>
<ScrollView style={styles.content}>
<Text> { this.state.error && this.state.error.toString()} </Text>
<Text> { this.state.errorInfo && this.state.errorInfo.componentStack } </Text>
</ScrollView>
</View>
</Modal>
) : <WrappedComponent {...this.props} />
);
}
}
}
export default withErrorBoundary;
复制代码
上面是一个React高阶组件,返回的组件定义了componentDidCatch
生命周期函数,当其子组件出现异常时,会执行此componentDidCatch
生命周期函数,渲染兜底UI
...
import withErrorBoundary from 'rn_components/exception_handler/with_error_boundary.js';
...
class ExceptionHandlerExample extends React.Component {
state = {
visible: false,
}
catch = () => {
console.log('catch');
this.setState({
visible: true,
});
}
render () {
if (this.state.visible) {
const a = d
}
return (
<View style={styles.container}> <Navbar title={'Exception Handler'} handleLeft={() => this.props.history.go(-1)}/> <View style={styles.content}> <TouchableOpacity onPress={this.catch}> <View> <Text>Click me</Text> </View> </TouchableOpacity> </View> </View> ); } } // 异常边界组件的使用 export default withErrorBoundary(ExceptionHandlerExample, (error, errorInfo) => { console.log('errorCallback', error, errorInfo); }, true); 复制代码
上面咱们也说过,异常边界组件能捕获子组件生命周期函数中的异常,包括构造函数(constructor)和render函数
而不能捕获如下异常:
因此须要使用 React Native ErrorUtils 模块对这些异常进行捕获并处理
React Native ErrorUtils 是负责对RN页面中异常进行管理的模块,功能很相似Web页面中的 window.onerror
首先咱们看看怎么利用 React Native ErrorUtils 进行异步捕获和处理,直接上代码:
const noop = () => {};
export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) => {
if (typeof allowedInDevMode !== "boolean" || typeof customHandler !== "function") {
return;
}
const allowed = allowedInDevMode ? true : !__DEV__;
if (allowed) {
// !!! 关键代码
// 设置错误处理函数
global.ErrorUtils.setGlobalHandler(customHandler);
// 改写 console.error,保证报错能被 ErrorUtils 捕获并调用错误处理函数处理
console.error = (message, error) => global.ErrorUtils.reportError(error);
}
};
export const getJSExceptionHandler = () => global.ErrorUtils.getGlobalHandler();
export default {
setJSExceptionHandler,
getJSExceptionHandler,
};
复制代码
上面关键的代码就两行,在注释中已标明
import { setJSExceptionHandler } from './error_guard';
import { Alert } from 'react-native';
setJSExceptionHandler((e, isFatal) => {
if (isFatal) {
Alert.alert(
'Unexpected error occurred',
` ${e && e.stack && e.stack.slice(0, 300)}... `,
[{
text: 'OK',
onPress: () => {
console.log('ok');
}
}]
);
} else {
console.log(e);
}
}, true);
复制代码
使用很简单,下面咱们来看看 ErrorUtils
模块的源码
本文源码是2018年9月10日拉取的React Native仓库master分支上的代码
首先看看 ErrorUtils 的定义,源码位置:Libraries/polyfills/error_guard.js
let _inGuard = 0;
let _globalHandler = function onError(e) {
throw e;
};
const ErrorUtils = {
setGlobalHandler(fun) {
_globalHandler = fun;
},
getGlobalHandler() {
return _globalHandler;
},
reportError(error) {
_globalHandler && _globalHandler(error);
},
reportFatalError(error) {
_globalHandler && _globalHandler(error, true);
},
...
};
global.ErrorUtils = ErrorUtils;
复制代码
上面只展现了咱们使用了的方法,咱们能够看到咱们改写的 console.error
,即 (message, error) => global.ErrorUtils.reportError(error)
,最终是执行的 _globalHandler
因此经过这种方法能够捕获到全部使用了 console.error
的异常,咱们来看看 React Native 源码中什么地方使用了 ErrorUtils 来作异常捕获和处理
来到 MessageQueue
源码,位置:Libraries/BatchedBridge/MessageQueue.js
__guard(fn: () => void) {
if (this.__shouldPauseOnThrow()) {
fn();
} else {
try {
fn();
} catch (error) {
ErrorUtils.reportFatalError(error);
}
}
}
复制代码
咱们能够看到上面这个__guard
方法中使用了try...catch...
对函数的执行进行守护,当发生异常时,会调用 ErrorUtils.reportFatalError(error);
对错误进行处理
使用了__guard
的地方这里就不一一列举了,咱们能够看看 MessageQueue
这个模块在RN中处于什么位置
由于没有系统的看过RN的源码,在网上找了个介绍 Native 和 JS 之间通讯的图,咱们能够看到 MessageQueue
在 Native 和 JS 之间通讯是很重要的模块
来到 BatchedBridge
源码,位置:Libraries/BatchedBridge/BatchedBridge.js
'use strict';
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();
Object.defineProperty(global, '__fbBatchedBridge', {
configurable: true,
value: BatchedBridge,
});
module.exports = BatchedBridge;
复制代码
熟悉RN的同窗应该知道,BatchedBridge
是 Native 和 JS 之间通讯的关键模块,从上面的源码咱们能够知道,BatchedBridge
实际就是MessageQueue
实例
因此在 MessageQueue
模块中使用 ErrorUtils 能捕获到全部通讯过程当中的异常并调用_globalHandler
处理
以上全部代码可在我的开发的RN组件库的项目中查看到:rn_components ExceptionHandler,组件库如今才刚开始建设,后续会不断完善
以上就是我对 React Native 异常处理分享,但愿能对有须要的小伙伴有帮助~~~
喜欢个人文章小伙伴能够去 个人我的博客 点star ⭐️