React Native 異常處理

原文地址:github.com/HuJiaoHJ/bl…react

React Native頁面出現錯誤時:git

一、開發模式下,會出現紅色背景的頁面,展現當前代碼錯誤信息github

二、bundle模式下,則會出現白屏或者閃退react-native

開發模式promise

rn_dev_error

bundle模式app

rn_bundle_error

在生產環境下,因RN頁面異常致使整個APP白屏或者閃退,用戶體驗並很差,因此應該對異常進行捕獲並處理,提升用戶體驗異步

主要使用兩種方法對RN頁面的異常進行捕獲並處理:ide

一、React Error Boundaries (異常邊界組件)函數

二、React Native ErrorUtils 模塊oop

React Error Boundaries (異常邊界組件)

React Error Boundaries (異常邊界組件)是React 16 引入的新概念,爲了不React的組件內的UI異常致使整個應用的異常

對React的異常邊界組件不熟悉的小夥伴能夠看看個人文章:從源碼看React異常處理

這裏簡單介紹下:

Error Boundaries(異常邊界)是React組件,用於捕獲它子組件樹種全部組件產生的js異常,並渲染指定的兜底UI來替代出問題的組件

它能捕獲子組件生命週期函數中的異常,包括構造函數(constructor)和render函數

而不能捕獲如下異常:

  • Event handlers(事件處理函數)
  • Asynchronous code(異步代碼,如setTimeout、promise等)
  • Server side rendering(服務端渲染)
  • Errors thrown in the error boundary itself (rather than its children)(異常邊界組件自己拋出的異常)

因此能夠經過異常邊界組件捕獲組件生命週期內的全部異常並渲染兜底UI,防止APP白屏或閃退,提升用戶體驗,也可在兜底UI中指引用戶反饋截圖反饋問題,方便問題的排查和修復

直接上代碼:

with_error_boundary.js

...
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函數

而不能捕獲如下異常:

  • Event handlers(事件處理函數)
  • Asynchronous code(異步代碼,如setTimeout、promise等)
  • Server side rendering(服務端渲染)
  • Errors thrown in the error boundary itself (rather than its children)(異常邊界組件自己拋出的異常)

因此須要使用 React Native ErrorUtils 模塊對這些異常進行捕獲並處理

React Native ErrorUtils 模塊

React Native ErrorUtils 是負責對RN頁面中異常進行管理的模塊,功能很相似Web頁面中的 window.onerror

首先咱們看看怎麼利用 React Native ErrorUtils 進行異步捕獲和處理,直接上代碼:

error_guard.js

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 模塊的源碼

ErrorUtils 源碼

本文源碼是2018年9月10日拉取的React Native倉庫master分支上的代碼

error_guard.js

首先看看 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.js

來到 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.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 ⭐️

相關文章
相關標籤/搜索