React-Navigation源碼閱讀-createAppContainer.js

代碼高亮

import React from 'react';
import { AsyncStorage, Linking, Platform, BackHandler } from 'react-native';
import { polyfill } from 'react-lifecycles-compat';

import {
  NavigationActions,
  pathUtils,
  getNavigation,
  NavigationProvider,
} from '@react-navigation/core';
import invariant from './utils/invariant';
import docsUrl from './utils/docsUrl';

const { urlToPathAndParams } = pathUtils;

function isStateful(props) {
  return !props.navigation;
}

function validateProps(props) {
  if (isStateful(props)) {
    return;
  }
  // eslint-disable-next-line no-unused-vars
  const { navigation, screenProps, ...containerProps } = props;

  const keys = Object.keys(containerProps);

  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.'
    );
  }
}

// Track the number of stateful container instances. Warn if >0 and not using the
// detached prop to explicitly acknowledge the behavior. We should deprecated implicit
// stateful navigation containers in a future release and require a provider style pattern
// instead in order to eliminate confusion entirely.
let _statefulContainerCount = 0;
export function _TESTING_ONLY_reset_container_count() {
  _statefulContainerCount = 0;
}

// We keep a global flag to catch errors during the state persistence hydrating scenario.
// The innermost navigator who catches the error will dispatch a new init action.
let _reactNavigationIsHydratingState = false;
// Unfortunate to use global state here, but it seems necessesary for the time
// being. There seems to be some problems with cascading componentDidCatch
// handlers. Ideally the inner non-stateful navigator catches the error and
// re-throws it, to be caught by the top-level stateful navigator.

/**
 * 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.
 */
export default function createNavigationContainer(Component) {
  class NavigationContainer extends React.Component {
    subs = null;

    static router = Component.router;
    static navigationOptions = null;

    static getDerivedStateFromProps(nextProps) {
      validateProps(nextProps);
      return null;
    }

    _actionEventSubscribers = new Set();

    constructor(props) {
      super(props);

      validateProps(props);

      this._initialAction = NavigationActions.init();

      if (this._isStateful()) {
        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());
          }
        });
      }

      this.state = {
        nav:
          this._isStateful() && !props.persistenceKey
            ? Component.router.getStateForAction(this._initialAction)
            : null,
      };
    }

    _renderLoading() {
      return this.props.renderLoadingExperimental
        ? this.props.renderLoadingExperimental()
        : null;
    }

    _isStateful() {
      return isStateful(this.props);
    }

    _validateProps(props) {
      if (this._isStateful()) {
        return;
      }

      // eslint-disable-next-line no-unused-vars
      const { navigation, screenProps, ...containerProps } = props;

      const keys = Object.keys(containerProps);

      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.'
        );
      }
    }

    _handleOpenURL = ({ url }) => {
      const { enableURLHandling, uriPrefix } = this.props;
      if (enableURLHandling === false) {
        return;
      }
      const parsedUrl = urlToPathAndParams(url, uriPrefix);
      if (parsedUrl) {
        const { path, params } = parsedUrl;
        const action = Component.router.getActionForPathAndParams(path, params);
        if (action) {
          this.dispatch(action);
        }
      }
    };

    _onNavigationStateChange(prevNav, nav, action) {
      if (
        typeof this.props.onNavigationStateChange === 'undefined' &&
        this._isStateful() &&
        !!process.env.REACT_NAV_LOGGING
      ) {
        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,
          });
        }
        return;
      }

      if (typeof this.props.onNavigationStateChange === 'function') {
        this.props.onNavigationStateChange(prevNav, nav, action);
      }
    }

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

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

      if (__DEV__ && !this.props.detached) {
        if (_statefulContainerCount > 0) {
          // Temporarily only show this on iOS due to this issue:
          // https://github.com/react-navigation/react-navigation/issues/4196#issuecomment-390827829
          if (Platform.OS === 'ios') {
            console.warn(
              `You should only render one navigator explicitly in your app, and other navigators should be rendered by including them in that navigator. Full details at: ${docsUrl(
                'common-mistakes.html#explicitly-rendering-more-than-one-navigator'
              )}`
            );
          }
        }
      }
      _statefulContainerCount++;
      Linking.addEventListener('url', this._handleOpenURL);

      // Pull out anything that can impact state
      const { persistenceKey, uriPrefix, enableURLHandling } = this.props;
      let parsedUrl = null;
      let startupStateJSON = null;
      if (enableURLHandling !== false) {
        startupStateJSON =
          persistenceKey && (await AsyncStorage.getItem(persistenceKey));
        const url = await Linking.getInitialURL();
        parsedUrl = url && urlToPathAndParams(url, uriPrefix);
      }

      // Initialize state. This must be done *after* any async code
      // so we don't end up with a different value for this.state.nav
      // due to changes while async function was resolving
      let action = this._initialAction;
      let startupState = this.state.nav;
      if (!startupState) {
        !!process.env.REACT_NAV_LOGGING &&
          console.log('Init new Navigation State');
        startupState = Component.router.getStateForAction(action);
      }

      // Pull persisted state from AsyncStorage
      if (startupStateJSON) {
        try {
          startupState = JSON.parse(startupStateJSON);
          _reactNavigationIsHydratingState = true;
        } catch (e) {
          /* do nothing */
        }
      }

      // Pull state out of URL
      if (parsedUrl) {
        const { path, params } = parsedUrl;
        const urlAction = Component.router.getActionForPathAndParams(
          path,
          params
        );
        if (urlAction) {
          !!process.env.REACT_NAV_LOGGING &&
            console.log(
              'Applying Navigation Action for Initial URL:',
              parsedUrl
            );
          action = urlAction;
          startupState = Component.router.getStateForAction(
            urlAction,
            startupState
          );
        }
      }

      const dispatchActions = () =>
        this._actionEventSubscribers.forEach(subscriber =>
          subscriber({
            type: 'action',
            action,
            state: this.state.nav,
            lastState: null,
          })
        );

      if (startupState === this.state.nav) {
        dispatchActions();
        return;
      }

      // eslint-disable-next-line react/no-did-mount-set-state
      this.setState({ nav: startupState }, () => {
        _reactNavigationIsHydratingState = false;
        dispatchActions();
      });
    }

    componentDidCatch(e) {
      if (_reactNavigationIsHydratingState) {
        _reactNavigationIsHydratingState = false;
        console.warn(
          'Uncaught exception while starting app from persisted navigation state! Trying to render again with a fresh navigation state..'
        );
        this.dispatch(NavigationActions.init());
      } else {
        throw e;
      }
    }

    _persistNavigationState = async nav => {
      const { persistenceKey } = this.props;
      if (!persistenceKey) {
        return;
      }
      await AsyncStorage.setItem(persistenceKey, JSON.stringify(nav));
    };

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

      if (this._isStateful()) {
        _statefulContainerCount--;
      }
    }

    // Per-tick temporary storage for state.nav

    dispatch = action => {
      if (this.props.navigation) {
        return this.props.navigation.dispatch(action);
      }

      // navState will have the most up-to-date value, because setState sometimes behaves asyncronously
      this._navState = this._navState || this.state.nav;
      const lastNavState = this._navState;
      invariant(lastNavState, 'should be set in constructor if stateful');
      const reducedState = Component.router.getStateForAction(
        action,
        lastNavState
      );
      const navState = reducedState === null ? lastNavState : reducedState;

      const dispatchActionEvents = () => {
        this._actionEventSubscribers.forEach(subscriber =>
          subscriber({
            type: 'action',
            action,
            state: navState,
            lastState: lastNavState,
          })
        );
      };

      if (reducedState === null) {
        // The router will return null when action has been handled and the state hasn't changed.
        // dispatch returns true when something has been handled.
        dispatchActionEvents();
        return true;
      }

      if (navState !== lastNavState) {
        // Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
        this._navState = navState;
        this.setState({ nav: navState }, () => {
          this._onNavigationStateChange(lastNavState, navState, action);
          dispatchActionEvents();
          this._persistNavigationState(navState);
        });
        return true;
      }

      dispatchActionEvents();
      return false;
    };

    _getScreenProps = () => this.props.screenProps;

    render() {
      let navigation = this.props.navigation;
      if (this._isStateful()) {
        const navState = this.state.nav;
        if (!navState) {
          return this._renderLoading();
        }
        if (!this._navigation || this._navigation.state !== navState) {
          this._navigation = getNavigation(
            Component.router,
            navState,
            this.dispatch,
            this._actionEventSubscribers,
            this._getScreenProps,
            () => this._navigation
          );
        }
        navigation = this._navigation;
      }
      invariant(navigation, 'failed to get navigation');
      return (
        <NavigationProvider value={navigation}>
          <Component {...this.props} navigation={navigation} />
        </NavigationProvider>
      );
    }
  }

  return polyfill(NavigationContainer);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末队丝,一起剝皮案震驚了整個濱河市芽隆,隨后出現(xiàn)的幾起案子预愤,更是在濱河造成了極大的恐慌奉呛,老刑警劉巖藏杖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懂盐,死亡現(xiàn)場離奇詭異竭恬,居然都是意外死亡跛蛋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門痊硕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赊级,“玉大人,你說我怎么就攤上這事岔绸±硌罚” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵盒揉,是天一觀的道長晋被。 經(jīng)常有香客問我,道長刚盈,這世上最難降的妖魔是什么羡洛? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮扁掸,結(jié)果婚禮上翘县,老公的妹妹穿的比我還像新娘最域。我一直安慰自己,他們只是感情好锈麸,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布镀脂。 她就那樣靜靜地躺著,像睡著了一般忘伞。 火紅的嫁衣襯著肌膚如雪薄翅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天氓奈,我揣著相機與錄音翘魄,去河邊找鬼。 笑死舀奶,一個胖子當著我的面吹牛暑竟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播育勺,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼但荤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涧至?” 一聲冷哼從身側(cè)響起腹躁,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎南蓬,沒想到半個月后纺非,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡赘方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年烧颖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒜焊。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡倒信,死狀恐怖科贬,靈堂內(nèi)的尸體忽然破棺而出泳梆,到底是詐尸還是另有隱情,我是刑警寧澤榜掌,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布优妙,位于F島的核電站,受9級特大地震影響憎账,放射性物質(zhì)發(fā)生泄漏套硼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一胞皱、第九天 我趴在偏房一處隱蔽的房頂上張望邪意。 院中可真熱鬧九妈,春花似錦、人聲如沸雾鬼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽策菜。三九已至晶疼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間又憨,已是汗流浹背翠霍。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蠢莺,地道東北人寒匙。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像躏将,于是被迫代替她去往敵國和親蒋情。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程耸携,因...
    小菜c閱讀 6,358評論 0 17
  • 目標簡單做個導航效果的Navigator 在接觸ReactNative中 渲染視圖最重要即render方法去渲染棵癣,...
    越長越圓閱讀 1,553評論 0 3
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情夺衍,實現(xiàn)同樣的效果;這時候需要使用工廠模式狈谊。簡單...
    舟漁行舟閱讀 7,718評論 2 17
  • 洛桑陀美上師:《和諧中的圓滿》—【法的功德】 法的功德,有這樣一個比喻沟沙,眾生包括我在內(nèi)就像病人一樣河劝,從往昔無...
    祥云_17ec閱讀 752評論 0 0
  • 一 新聞導入新課 新聞有關手機定位后面的秘密 看完視頻,你看到了什么矛紫?想到了什么赎瞎? (隱私被曝光) 如果企業(yè)商業(yè)的...
    辛德秋水閱讀 595評論 0 3