React Navigation源代码阅读 : createNavigationContainer.js

import React from 'react';
import {Linking} from 'react-native';
import {BackHandler} from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant';

/**
 * Create an HOC that injects the navigation and manages the navigation state
 * in case it's not passed from above.
 * This allows to use e.g. the StackNavigator and TabNavigator as root-level
 * components.
 *
 * 高阶组件,封装一个组件(实际上是一个navigator导航器组件)并向其注入导航属性(navigation),
 * 并在导航状态没有从上面传入时管理导航状态,这样 StackNavigator和 TabNavigator 就可以被
 * 作为顶层组件来使用
 *
 * @param Component 由 createNavigator 创建的 navigator 组件,它有一个静态属性 router,表示路由器
 * @returns {NavigationContainer}
 */
export default function createNavigationContainer(Component) {
    class NavigationContainer extends React.Component {
        subs = null;

        // 获取 导航器组件的静态属性 router
        static router = Component.router;
        static navigationOptions = null;

        // action 事件监听器,事件订阅者 的集合
        _actionEventSubscribers = new Set();

        constructor(props) {
            super(props);

            // 验证当前组件的属性 this.props 的格式是否正确 :
            // 1. 如果这是一个有状态组件,不验证直接返回;
            // 2. 如果这是一个无状态组件, 那它的属性 this.props 中只能最多包含两个属性 :
            // navigation,screenProps,否则抛出Error
            this._validateProps(props);

            this._initialAction = NavigationActions.init();

            if (this._isStateful()) {
                // 增加对硬件事件 hardwareBackPress (返回按钮被按下) 的事件监听逻辑并记录该订阅
                this.subs = BackHandler.addEventListener('hardwareBackPress', () => {
                    if (!this._isMounted) {
                        this.subs && this.subs.remove();
                    } else {
                        // dispatch returns true if the action results in a state change,
                        // and false otherwise. This maps well to what BackHandler expects
                        // from a callback -- true if handled, false if not handled
                        return this.dispatch(NavigationActions.back());
                    }
                });
            }

            // 当前组件状态的初始化: 如果时有状态组件,nav 初始化为路由器的初始状态, 否则设置为 null
            this.state = {
                nav: this._isStateful()
                    ? Component.router.getStateForAction(this._initialAction)
                    : null,
            };
        }

        /**
         * 检测当前组件是否是状态组件
         * @returns {boolean}
         * @private
         */
        _isStateful() {
            // 如果当前组件实例的 this.props.navigation 属性没有提供,则认为当前组件是一个有状态组件,
            return !this.props.navigation;
        }

        /**
         * 验证当前组件的属性 this.props 的格式是否正确 :
         * 1. 如果这是一个有状态组件,不验证直接返回;
         * 2. 如果这是一个无状态组件, 那它的属性 this.props 中只能最多包含两个属性 :
         *   navigation,screenProps, 否则抛出Error
         * @param props
         * @private
         */
        _validateProps(props) {
            if (this._isStateful()) {
                // 如果这是一个有状态组件(this.props.navigation),那么不需要验证 this.props
                return;
            }

            const {navigation, screenProps, ...containerProps} = props;

            const keys = Object.keys(containerProps);

            // 这里其实隐含地说明了一个限制:
            // 1. 当前组件要么是有状态的,通过组件实例属性(除this.props之外的其它实例属性)来管理状态,
            // 2. 要么是无状态的,通过 this.props.navigation 属性来管理状态,
            // 这两种情况是互斥的,下面的if语句部分说明了这一点,当 this.props 中包含了除
            // {navigation,screenProps}之外其他任何属性的时候,就认为不知道是上面两种情况中的哪一种了,
            // 这里的措施是直接抛出一个 Error
            if (keys.length !== 0) {
                throw new Error(
                    'This navigator has both navigation and container props, so it is ' +
                    `unclear if it should own its own state. Remove props: "${keys.join(
                        ', '
                    )}" ` +
                    'if the navigator should get its state from the navigation prop. If the ' +
                    'navigator should maintain its own state, do not pass a navigation prop.'
                );
            }
        }

        /**
         * 工具函数,从名字上看,将一个 url 分解成路径 path 部分和参数 params 部分,
         * 1.实际上该函数并没有分析 url 中的参数,所以返回的参数部分为 {}
         * 2.所分析得到的路径 path 部分指的是 url 中协议分隔符 :// 后面的部分
         * 举例:
         *     参数 url 是 : "https://www.google.com?q=123"
         *     返回结果是 : { path: 'www.google.com?q=123', params: {} }
         * @param url
         * @returns {{path: (*|string), params}}
         * @private
         */
        _urlToPathAndParams(url) {
            const params = {};
            const delimiter = this.props.uriPrefix || '://';
            let path = url.split(delimiter)[1];
            if (typeof path === 'undefined') {
                path = url;
            } else if (path === '') {
                path = '/';
            }
            return {
                path,
                params,
            };
        }

        /**
         * 处理打开一个 url
         * @param url
         * @private
         */
        _handleOpenURL = ({url}) => {
            const parsedUrl = this._urlToPathAndParams(url);
            if (parsedUrl) {
                const {path, params} = parsedUrl;
                const action = Component.router.getActionForPathAndParams(path, params);
                if (action) {
                    this.dispatch(action);
                }
            }
        };

        /**
         * 某个指令 action 导致了导航状态发生变化时此方法会被调用,并且如果外部指定了
         * this.props.onNavigationStateChange 的话,this.props.onNavigationStateChange
         * 会被调用,参数列表和本函数一样
         * @param prevNav action 执行之前的导航状态
         * @param nav action 执行之后的导航状态
         * @param action 导致状态发生变化的 acction
         * @private
         */
        _onNavigationStateChange(prevNav, nav, action) {
            if (
                typeof this.props.onNavigationStateChange === 'undefined' &&
                this._isStateful() &&
                !!process.env.REACT_NAV_LOGGING
            ) {
                /* eslint-disable no-console */
                if (console.group) {
                    console.group('Navigation Dispatch: ');
                    console.log('Action: ', action);
                    console.log('New State: ', nav);
                    console.log('Last State: ', prevNav);
                    console.groupEnd();
                } else {
                    console.log('Navigation Dispatch: ', {
                        action,
                        newState: nav,
                        lastState: prevNav,
                    });
                }
                /* eslint-enable no-console */
                return;
            }

            // 如果外部指定了 this.props.onNavigationStateChange ,
            // 调用 this.props.onNavigationStateChange
            if (typeof this.props.onNavigationStateChange === 'function') {
                this.props.onNavigationStateChange(prevNav, nav, action);
            }
        }

        componentWillReceiveProps(nextProps) {
            this._validateProps(nextProps);
        }

        componentDidUpdate() {
            // Clear cached _nav every tick
            if (this._nav === this.state.nav) {
                this._nav = null;
            }
        }

        componentDidMount() {
            this._isMounted = true;
            if (!this._isStateful()) {
                return;
            }

            Linking.addEventListener('url', this._handleOpenURL);

            Linking.getInitialURL().then(url => url && this._handleOpenURL({url}));

            /**
             * 向每个 action 事件订阅者通知 初始化 action
             */
            this._actionEventSubscribers.forEach(subscriber =>
                subscriber({
                    type: 'action',
                    action: this._initialAction,
                    state: this.state.nav,
                    lastState: null,
                })
            );
        }

        componentWillUnmount() {
            this._isMounted = false;
            Linking.removeEventListener('url', this._handleOpenURL);
            this.subs && this.subs.remove();
        }

        // Per-tick temporary storage for state.nav
        /**
         * 派发 action
         * @param action
         * @returns {boolean}
         */
        dispatch = action => {
            if (!this._isStateful()) {
                // 如果当前组件是无状态的,则不做事件派发
                return false;
            }
            this._nav = this._nav || this.state.nav;// 获取当前导航状态到 this._nav
            const oldNav = this._nav; // 将当前导航状态记录到 oldNav,
            // 因为接下来要根据这个状态和要派发的 action 来计算 action 后可能到达的状态,
            // 所以这里记录为 oldNav 可以和目标状态有个明确的区分
            invariant(oldNav, 'should be set in constructor if stateful');
            // 让路由器基于当前导航状态 oldNav 和待派发事件 action 计算出目标导航状态是什么,记录到 nav
            const nav = Component.router.getStateForAction(action, oldNav);
            // 准备一个函数,用于向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
            const dispatchActionEvents = () => {
                this._actionEventSubscribers.forEach(subscriber =>
                    subscriber({
                        type: 'action',
                        action,
                        state: nav,
                        lastState: oldNav,
                    })
                );
            };
            if (nav && nav !== oldNav) {
                // 如果当前导航状态 oldNav 和待派发事件 action计算出的目标 nav 和当前的 oldNav 不同,
                // 则要通过this.setState()设置最新导航状态 nav,并在设置动作完成时发布相关事件和进行
                // 回调调用,这里需要注意的一点是 this.setState()是一步的,它的第二个参数是异步任务完
                // 成时的回调逻辑
                // Cache updates to state.nav during the tick to ensure that subsequent calls will
                // not discard this change
                this._nav = nav;
                this.setState({nav}, () => {
                    this._onNavigationStateChange(oldNav, nav, action);
                    // 向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
                    dispatchActionEvents();
                });
                return true;
            } else {
                // 向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
                dispatchActionEvents();
            }
            return false;
        };

        render() {
            let navigation = this.props.navigation;
            if (this._isStateful()) {
                const nav = this.state.nav;
                invariant(nav, 'should be set in constructor if stateful');
                if (!this._navigation || this._navigation.state !== nav) {
                    // 如果导航状态发生了变化,重新构建一个 navigation 对象并做相应的增强,
                    // 增强逻辑参考 addNavigationHelpers 的函数的实现
                    this._navigation = addNavigationHelpers({
                        dispatch: this.dispatch, // 管理 action 派发函数
                        state: nav, // 关联新的导航状态
                        addListener: (eventName, handler) => {
                            if (eventName !== 'action') {
                                return {
                                    remove: () => {
                                    }
                                };
                            }
                            this._actionEventSubscribers.add(handler);
                            return {
                                remove: () => {
                                    this._actionEventSubscribers.delete(handler);
                                },
                            };
                        },
                    });
                }
                navigation = this._navigation;
            }
            invariant(navigation, 'failed to get navigation');
            return <Component {...this.props} navigation={navigation}/>;
        }
    }

    return NavigationContainer;
}

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/80341385