mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-09 09:13:32 +08:00
208 lines
5.4 KiB
JavaScript
208 lines
5.4 KiB
JavaScript
/* @flow */
|
|
|
|
import React from 'react';
|
|
import invariant from './utils/invariant';
|
|
import { BackAndroid, Linking } from './PlatformHelpers';
|
|
import NavigationActions from './NavigationActions';
|
|
import addNavigationHelpers from './addNavigationHelpers';
|
|
|
|
import type {
|
|
NavigationRoute,
|
|
NavigationAction,
|
|
NavigationState,
|
|
NavigationScreenProp,
|
|
NavigationNavigatorProps,
|
|
} from './TypeDefinition';
|
|
|
|
type NavigationContainerProps = {
|
|
uriPrefix?: string | RegExp,
|
|
onNavigationStateChange?: (
|
|
NavigationState,
|
|
NavigationState,
|
|
NavigationAction
|
|
) => void,
|
|
};
|
|
|
|
type Props<T> = NavigationContainerProps & NavigationNavigatorProps<T>;
|
|
|
|
type State = {
|
|
nav: ?NavigationState,
|
|
};
|
|
|
|
/**
|
|
* 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<T: *>(
|
|
Component: ReactClass<NavigationNavigatorProps<T>>,
|
|
containerOptions?: {}
|
|
) {
|
|
invariant(
|
|
typeof containerOptions === 'undefined',
|
|
'containerOptions.URIPrefix has been removed. Pass the uriPrefix prop to the navigator instead'
|
|
);
|
|
|
|
class NavigationContainer extends React.Component<void, Props<T>, State> {
|
|
state: State;
|
|
props: Props<T>;
|
|
|
|
subs: ?{
|
|
remove: () => void,
|
|
} = null;
|
|
|
|
static router = Component.router;
|
|
|
|
constructor(props: Props<T>) {
|
|
super(props);
|
|
|
|
this._validateProps(props);
|
|
|
|
this.state = {
|
|
nav: this._isStateful()
|
|
? Component.router.getStateForAction(NavigationActions.init())
|
|
: null,
|
|
};
|
|
}
|
|
|
|
_isStateful(): boolean {
|
|
return !this.props.navigation;
|
|
}
|
|
|
|
_validateProps(props: Props<T>) {
|
|
if (this._isStateful()) {
|
|
return;
|
|
}
|
|
|
|
const { navigation, screenProps, ...containerProps } = props;
|
|
|
|
const keys = Object.keys(containerProps);
|
|
|
|
invariant(
|
|
keys.length === 0,
|
|
'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.'
|
|
);
|
|
}
|
|
|
|
_urlToPathAndParams(url: string) {
|
|
const params = {};
|
|
const delimiter = this.props.uriPrefix || '://';
|
|
let path = url.split(delimiter)[1];
|
|
if (!path) {
|
|
path = url;
|
|
}
|
|
return {
|
|
path,
|
|
params,
|
|
};
|
|
}
|
|
|
|
_handleOpenURL = (url: string) => {
|
|
const parsedUrl = this._urlToPathAndParams(url);
|
|
if (parsedUrl) {
|
|
const { path, params } = parsedUrl;
|
|
const action = Component.router.getActionForPathAndParams(path, params);
|
|
if (action) {
|
|
this.dispatch(action);
|
|
}
|
|
}
|
|
};
|
|
|
|
_onNavigationStateChange(
|
|
prevNav: NavigationState,
|
|
nav: NavigationState,
|
|
action: NavigationAction
|
|
) {
|
|
if (
|
|
typeof this.props.onNavigationStateChange === 'undefined' &&
|
|
this._isStateful()
|
|
) {
|
|
/* 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;
|
|
}
|
|
|
|
if (typeof this.props.onNavigationStateChange === 'function') {
|
|
this.props.onNavigationStateChange(prevNav, nav, action);
|
|
}
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps: *) {
|
|
this._validateProps(nextProps);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (!this._isStateful()) {
|
|
return;
|
|
}
|
|
|
|
this.subs = BackAndroid.addEventListener('backPress', () =>
|
|
this.dispatch(NavigationActions.back())
|
|
);
|
|
|
|
Linking.addEventListener('url', ({ url }: { url: string }) => {
|
|
this._handleOpenURL(url);
|
|
});
|
|
|
|
Linking.getInitialURL().then(
|
|
(url: string) => url && this._handleOpenURL(url)
|
|
);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
Linking.removeEventListener('url', this._handleOpenURL);
|
|
this.subs && this.subs.remove();
|
|
}
|
|
|
|
dispatch = (action: NavigationAction) => {
|
|
const { state } = this;
|
|
if (!this._isStateful()) {
|
|
return false;
|
|
}
|
|
const nav = Component.router.getStateForAction(action, state.nav);
|
|
if (nav && nav !== state.nav) {
|
|
this.setState({ nav }, () =>
|
|
this._onNavigationStateChange(state.nav, nav, action)
|
|
);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
_navigation: ?NavigationScreenProp<NavigationRoute, NavigationAction>;
|
|
|
|
render() {
|
|
let navigation = this.props.navigation;
|
|
if (this._isStateful()) {
|
|
if (!this._navigation || this._navigation.state !== this.state.nav) {
|
|
this._navigation = addNavigationHelpers({
|
|
dispatch: this.dispatch,
|
|
state: this.state.nav,
|
|
});
|
|
}
|
|
navigation = this._navigation;
|
|
}
|
|
return <Component {...this.props} navigation={navigation} />;
|
|
}
|
|
}
|
|
|
|
return NavigationContainer;
|
|
}
|