原文地址: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 ⭐️