Original address: github.com/HuJiaoHJ/bl…

React Native page error:

1. In development mode, a page with a red background will appear, showing the error information of the current code

2. In bundle mode, a blank screen or a flash rollback is displayed

Development mode

Bundle model

In the production environment, the entire APP displays a blank screen or blinks due to an EXCEPTION on an RN page. Therefore, you need to capture and handle exceptions to improve user experience

There are two main methods to catch and handle exceptions on RN pages:

1, React Error Boundaries

React Native ErrorUtils module

React Error Boundaries

React Error Boundaries are a new concept introduced in React 16 to prevent UI exceptions in React components from causing app-wide exceptions

React exception handlers are not familiar with the React exception boundary component

Here is a brief introduction:

Error Boundaries are the React component that captures js exceptions generated by all components of its subcomponent tree and renders the specified pocket UI to replace the offending component

It catches exceptions in child component lifecycle functions, including constructors and render functions

The following exceptions cannot be caught:

  • Event Handlers
  • Asynchronous code (e.g. SetTimeout, promise, etc.)
  • Server Side Rendering (Server-side Rendering)
  • Error thrown in the error boundary itself (rather than its children)

Therefore, the exception boundary component can be used to capture all anomalies within the life cycle of the component and render the bottom of the pocket UI to prevent the APP from going blank or flashing back and improve the user experience. The user can also be guided to feed back screenshots of problems in the bottom of the pocket UI to facilitate the investigation and repair of problems

Directly on the code:

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 = (a)= >{... } 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;
Copy the code

ComponentDidCatch () {React} componentDidCatch () {React} componentDidCatch () {React} componentDidCatch () {React} componentDidCatch () {React

use

. import withErrorBoundaryfrom 'rn_components/exception_handler/with_error_boundary.js'; . class ExceptionHandlerExample extends React.Component { state = {visible: false,}catch = (a)= > {
        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);Copy the code

As mentioned above, exception bound components can catch exceptions in child component lifecycle functions, including constructors and render functions

The following exceptions cannot be caught:

  • Event Handlers
  • Asynchronous code (e.g. SetTimeout, promise, etc.)
  • Server Side Rendering (Server-side Rendering)
  • Error thrown in the error boundary itself (rather than its children)

Therefore, use the React Native ErrorUtils module to catch and handle these exceptions

React Native ErrorUtils module

React Native ErrorUtils is a module that manages exceptions in RN pages. Its function is similar to window.onerror in Web pages

React Native ErrorUtils = React Native ErrorUtils

error_guard.js

const noop = (a)= > {};

export const setJSExceptionHandler = (customHandler = noop, allowedInDevMode = false) = > {
    if (typeofallowedInDevMode ! = ="boolean" || typeofcustomHandler ! = ="function") {
        return;
    }
    const allowed = allowedInDevMode ? true : !__DEV__;
    if (allowed) {
        / /!!!!!! The key code
        // Set the error handler
        global.ErrorUtils.setGlobalHandler(customHandler);
        // Overwrite console.error to ensure that ErrorUtils catches the error and calls the error handler
        console.error = (message, error) = >global.ErrorUtils.reportError(error); }};export const getJSExceptionHandler = (a)= > global.ErrorUtils.getGlobalHandler();

export default {
    setJSExceptionHandler,
    getJSExceptionHandler,
};
Copy the code

The key code above is just two lines, indicated in the comment

use

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: (a)= > {
                    console.log('ok'); }}]); }else {
        console.log(e); }},true);
Copy the code

Use is very simple, let’s take a look at the ErrorUtils module source

ErrorUtils source

The React Native repository master branch was installed on September 10, 2018

error_guard.js

Look first at ErrorUtils definition, source location: 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;Copy the code

Only shows that we use the method of above, we can see we rewrite the console. The error, namely (message, error) = > global. ErrorUtils. ReportError (error), Finally, the _globalHandler is executed

The React Native source code uses ErrorUtils to catch all exceptions using console.error

MessageQueue.js

Came to the MessageQueue source, location: Libraries/BatchedBridge/MessageQueue. Js

__guard(fn: (a)= > void) {
    if (this.__shouldPauseOnThrow()) {
        fn();
    } else {
        try {
            fn();
        } catch(error) { ErrorUtils.reportFatalError(error); }}}Copy the code

We can see that the __guard method uses a try… catch… For the implementation of the function, when an exception occurs, will call ErrorUtils. ReportFatalError (error); Handle errors

Where __guard is used is not listed here, but where is the MessageQueue module in RN

Since we have not read the source code of RN systematically, we found a map on the Internet to introduce the communication between Native and JS. We can see that MessageQueue is a very important module in the communication between Native and JS

BatchedBridge.js

Came to BatchedBridge source, location: 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;
Copy the code

Those familiar with RN should know that BatchedBridge is a key module for communication between Native and JS. From the source code above, we can know that BatchedBridge is actually an instance of MessageQueue

So using ErrorUtils in the MessageQueue module catches all communication exceptions and calls _globalHandler to handle them

All of the above code can be viewed in the project of my RN component library: RN_Components ExceptionHandler. The component library is just under construction and will continue to be improved

Write in the last

React Native exception handling

If you like my articles, go to my personal blog star ⭐️