mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-13 09:39:18 +08:00
Breaking: Replace containerOptions with just props (#1100)
This commit is contained in:
@@ -52,10 +52,6 @@ For the purpose of convenience, the built-in navigators have this ability becaus
|
||||
|
||||
Sometimes it is useful to know when navigation state managed by the top-level navigator changes. For this purpose, this function gets called every time with the previous state and the new state of the navigation.
|
||||
|
||||
### `containerOptions`
|
||||
### `uriPrefix`
|
||||
|
||||
These options can be used to configure a navigator when it is used at the top level.
|
||||
|
||||
An error will be thrown if a navigator is configured with `containerOptions` and also receives a `navigation` prop, because in that case it would be unclear if the navigator should handle its own state.
|
||||
|
||||
- `URIPrefix` - The prefix of the URIs that the app might handle. This will be used when handling a [deep link](/docs/guides/linking) to extract the path passed to the router.
|
||||
The prefix of the URIs that the app might handle. This will be used when handling a [deep link](/docs/guides/linking) to extract the path passed to the router.
|
||||
|
||||
@@ -30,17 +30,15 @@ const SimpleApp = StackNavigator({
|
||||
|
||||
### URI Prefix
|
||||
|
||||
Next, let's configure our navigation container to extract the path from the app's incoming URI. When configuring a top-level navigator, we can provide `containerOptions`:
|
||||
Next, let's configure our navigation container to extract the path from the app's incoming URI.
|
||||
|
||||
```js
|
||||
const SimpleApp = StackNavigator({
|
||||
...
|
||||
}, {
|
||||
containerOptions: {
|
||||
// on Android, the URI prefix typically contains a host in addition to scheme
|
||||
URIPrefix: Platform.OS == 'android' ? 'mychat://mychat/' : 'mychat://',
|
||||
},
|
||||
});
|
||||
const SimpleApp = StackNavigator({...});
|
||||
|
||||
// on Android, the URI prefix typically contains a host in addition to scheme
|
||||
const prefix = Platform.OS == 'android' ? 'mychat://mychat/' : 'mychat://';
|
||||
|
||||
const MainApp = () => <SimpleApp uriPrefix={prefix} />;
|
||||
```
|
||||
|
||||
## iOS
|
||||
|
||||
14
packages/react-navigation/src/TypeDefinition.js
vendored
14
packages/react-navigation/src/TypeDefinition.js
vendored
@@ -186,15 +186,6 @@ export type NavigationUriAction = {
|
||||
uri: string,
|
||||
};
|
||||
|
||||
export type NavigationContainerOptions = {
|
||||
// This is used to extract the path from the URI passed to the app for a deep link
|
||||
URIPrefix?: string,
|
||||
};
|
||||
|
||||
export type NavigationContainerConfig = {
|
||||
containerOptions?: NavigationContainerOptions,
|
||||
};
|
||||
|
||||
export type NavigationStackViewConfig = {
|
||||
mode?: 'card' | 'modal',
|
||||
headerMode?: HeaderMode,
|
||||
@@ -301,9 +292,10 @@ export type NavigationScreenProp<S, A> = {
|
||||
setParams: (newParams: NavigationParams) => boolean,
|
||||
};
|
||||
|
||||
export type NavigationNavigatorProps = {
|
||||
navigation: NavigationProp<NavigationRoute, NavigationAction>,
|
||||
export type NavigationNavigatorProps<T> = {
|
||||
navigation: NavigationProp<T, NavigationAction>,
|
||||
screenProps: *,
|
||||
navigationOptions: *,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,12 +12,22 @@ import addNavigationHelpers from './addNavigationHelpers';
|
||||
import type {
|
||||
NavigationRoute,
|
||||
NavigationAction,
|
||||
NavigationContainerOptions,
|
||||
NavigationProp,
|
||||
NavigationState,
|
||||
NavigationScreenProp,
|
||||
NavigationNavigatorProps,
|
||||
} from './TypeDefinition';
|
||||
|
||||
type NavigationContainerProps = {
|
||||
uriPrefix?: string,
|
||||
onNavigationStateChange?: (NavigationState, NavigationState) => 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.
|
||||
@@ -25,35 +35,17 @@ import type {
|
||||
* components.
|
||||
*/
|
||||
export default function createNavigationContainer<T: *>(
|
||||
Component: ReactClass<*>,
|
||||
containerConfig?: NavigationContainerOptions
|
||||
Component: ReactClass<NavigationNavigatorProps<T>>,
|
||||
containerOptions?: {},
|
||||
) {
|
||||
type Props = {
|
||||
navigation: NavigationProp<T, NavigationAction>,
|
||||
onNavigationStateChange?: (NavigationState, NavigationState) => void,
|
||||
};
|
||||
invariant(
|
||||
typeof containerOptions === 'undefined',
|
||||
'containerOptions.URIPrefix has been removed. Pass the uriPrefix prop to the navigator instead',
|
||||
);
|
||||
|
||||
type State = {
|
||||
nav: ?NavigationState,
|
||||
};
|
||||
|
||||
function urlToPathAndParams(url: string) {
|
||||
const params = {};
|
||||
const URIPrefix = containerConfig && containerConfig.URIPrefix;
|
||||
const delimiter = URIPrefix || '://';
|
||||
let path = url.split(delimiter)[1];
|
||||
if (!path) {
|
||||
path = url;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
class NavigationContainer extends React.Component {
|
||||
class NavigationContainer extends React.Component<void, Props<T>, State> {
|
||||
state: State;
|
||||
props: Props;
|
||||
props: Props<T>;
|
||||
|
||||
subs: ?{
|
||||
remove: () => void,
|
||||
@@ -61,23 +53,11 @@ export default function createNavigationContainer<T: *>(
|
||||
|
||||
static router = Component.router;
|
||||
|
||||
_isStateful: () => boolean = () => {
|
||||
const hasNavProp = !!this.props.navigation;
|
||||
if (hasNavProp) {
|
||||
invariant(
|
||||
!containerConfig,
|
||||
'This navigator has a container config AND a navigation prop, so it is ' +
|
||||
'unclear if it should own its own state. Remove the containerConfig ' +
|
||||
'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.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props<T>) {
|
||||
super(props);
|
||||
|
||||
this._validateProps(props);
|
||||
|
||||
this.state = {
|
||||
nav: this._isStateful()
|
||||
? Component.router.getStateForAction(NavigationActions.init())
|
||||
@@ -85,50 +65,46 @@ export default function createNavigationContainer<T: *>(
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
_isStateful(): boolean {
|
||||
return !this.props.navigation;
|
||||
}
|
||||
|
||||
_validateProps(props: Props<T>) {
|
||||
if (this._isStateful()) {
|
||||
this.subs = BackAndroid.addEventListener('backPress', () =>
|
||||
this.dispatch(NavigationActions.back())
|
||||
);
|
||||
Linking.addEventListener('url', this._handleOpenURL);
|
||||
Linking.getInitialURL().then((url: string) => {
|
||||
if (url) {
|
||||
console.log('Handling URL:', url);
|
||||
const parsedUrl = urlToPathAndParams(url);
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const action = Component.router.getActionForPathAndParams(path, params);
|
||||
if (action) {
|
||||
this.dispatch(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
navigation, screenProps, navigationOptions, onNavigationStateChange,
|
||||
...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.',
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
const [prevNavigationState, navigationState] = this._isStateful()
|
||||
? [prevState.nav, this.state.nav]
|
||||
: [prevProps.navigation.state, this.props.navigation.state];
|
||||
|
||||
if (
|
||||
prevNavigationState !== navigationState
|
||||
&& typeof this.props.onNavigationStateChange === 'function'
|
||||
) {
|
||||
// $FlowFixMe state is always defined, either this.state or props
|
||||
this.props.onNavigationStateChange(prevNavigationState, navigationState);
|
||||
_urlToPathAndParams(url: string) {
|
||||
const params = {};
|
||||
const delimiter = this.props.uriPrefix || '://';
|
||||
let path = url.split(delimiter)[1];
|
||||
if (!path) {
|
||||
path = url;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Linking.removeEventListener('url', this._handleOpenURL);
|
||||
this.subs && this.subs.remove();
|
||||
}
|
||||
|
||||
_handleOpenURL = ({ url }: { url: string }) => {
|
||||
console.log('Handling URL:', url);
|
||||
const parsedUrl = urlToPathAndParams(url);
|
||||
_handleOpenURL = (url: string) => {
|
||||
const parsedUrl = this._urlToPathAndParams(url);
|
||||
if (parsedUrl) {
|
||||
const { path, params } = parsedUrl;
|
||||
const action = Component.router.getActionForPathAndParams(path, params);
|
||||
@@ -138,13 +114,51 @@ export default function createNavigationContainer<T: *>(
|
||||
}
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props<T>, prevState: State) {
|
||||
const [prevNavigationState, navigationState] = this._isStateful()
|
||||
? [prevState.nav, this.state.nav]
|
||||
: [prevProps.navigation.state, this.props.navigation.state];
|
||||
|
||||
if (
|
||||
prevNavigationState !== navigationState
|
||||
&& typeof this.props.onNavigationStateChange === 'function'
|
||||
) {
|
||||
this.props.onNavigationStateChange(prevNavigationState, navigationState);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (console.group) {
|
||||
console.group('Navigation Dispatch: ');
|
||||
@@ -168,7 +182,7 @@ export default function createNavigationContainer<T: *>(
|
||||
if (this._isStateful()) {
|
||||
if (!this._navigation || this._navigation.state !== this.state.nav) {
|
||||
this._navigation = addNavigationHelpers({
|
||||
dispatch: this.dispatch.bind(this),
|
||||
dispatch: this.dispatch,
|
||||
state: this.state.nav,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ import NavigatorTypes from './NavigatorTypes';
|
||||
|
||||
import type { DrawerViewConfig } from '../views/Drawer/DrawerView';
|
||||
import type {
|
||||
NavigationContainerConfig,
|
||||
NavigationRouteConfigMap,
|
||||
NavigationTabRouterConfig,
|
||||
} from '../TypeDefinition';
|
||||
|
||||
export type DrawerNavigatorConfig =
|
||||
& NavigationContainerConfig
|
||||
& { containerConfig?: void }
|
||||
& NavigationTabRouterConfig
|
||||
& DrawerViewConfig;
|
||||
|
||||
@@ -48,11 +47,12 @@ const DrawerNavigator = (
|
||||
drawerPosition,
|
||||
...tabsConfig
|
||||
} = mergedConfig;
|
||||
|
||||
const contentRouter = TabRouter(routeConfigs, tabsConfig);
|
||||
const drawerRouter = TabRouter({
|
||||
DrawerClose: {
|
||||
screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)((props: *) =>
|
||||
<DrawerScreen {...props} />
|
||||
screen: createNavigator(contentRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
|
||||
(props: *) => <DrawerScreen {...props} />,
|
||||
),
|
||||
},
|
||||
DrawerOpen: {
|
||||
@@ -61,15 +61,20 @@ const DrawerNavigator = (
|
||||
}, {
|
||||
initialRouteName: 'DrawerClose',
|
||||
});
|
||||
return createNavigationContainer(createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)((props: *) =>
|
||||
<DrawerView
|
||||
{...props}
|
||||
drawerWidth={drawerWidth}
|
||||
contentComponent={contentComponent}
|
||||
contentOptions={contentOptions}
|
||||
drawerPosition={drawerPosition}
|
||||
/>
|
||||
), containerConfig);
|
||||
|
||||
const navigator = createNavigator(drawerRouter, routeConfigs, config, NavigatorTypes.DRAWER)(
|
||||
(props: *) => (
|
||||
<DrawerView
|
||||
{...props}
|
||||
drawerWidth={drawerWidth}
|
||||
contentComponent={contentComponent}
|
||||
contentOptions={contentOptions}
|
||||
drawerPosition={drawerPosition}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
return createNavigationContainer(navigator, containerConfig);
|
||||
};
|
||||
|
||||
export default DrawerNavigator;
|
||||
|
||||
@@ -8,20 +8,21 @@ import StackRouter from '../routers/StackRouter';
|
||||
import NavigatorTypes from './NavigatorTypes';
|
||||
|
||||
import type {
|
||||
NavigationContainerConfig,
|
||||
NavigationStackRouterConfig,
|
||||
NavigationStackViewConfig,
|
||||
NavigationRouteConfigMap,
|
||||
} from '../TypeDefinition';
|
||||
|
||||
export type StackNavigatorConfig =
|
||||
& NavigationContainerConfig
|
||||
& { containerOptions?: void }
|
||||
& NavigationStackViewConfig
|
||||
& NavigationStackRouterConfig;
|
||||
|
||||
export default (routeConfigMap: NavigationRouteConfigMap, stackConfig: StackNavigatorConfig = {}) => {
|
||||
export default (
|
||||
routeConfigMap: NavigationRouteConfigMap,
|
||||
stackConfig: StackNavigatorConfig = {},
|
||||
) => {
|
||||
const {
|
||||
containerOptions,
|
||||
initialRouteName,
|
||||
initialRouteParams,
|
||||
paths,
|
||||
@@ -39,16 +40,21 @@ export default (routeConfigMap: NavigationRouteConfigMap, stackConfig: StackNavi
|
||||
paths,
|
||||
navigationOptions,
|
||||
};
|
||||
|
||||
const router = StackRouter(routeConfigMap, stackRouterConfig);
|
||||
return createNavigationContainer(createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)(props => (
|
||||
<CardStackTransitioner
|
||||
{...props}
|
||||
headerComponent={headerComponent}
|
||||
headerMode={headerMode}
|
||||
mode={mode}
|
||||
cardStyle={cardStyle}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
)), containerOptions);
|
||||
const navigator = createNavigator(router, routeConfigMap, stackConfig, NavigatorTypes.STACK)(
|
||||
(props: *) => (
|
||||
<CardStackTransitioner
|
||||
{...props}
|
||||
headerComponent={headerComponent}
|
||||
headerMode={headerMode}
|
||||
mode={mode}
|
||||
cardStyle={cardStyle}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
return createNavigationContainer(navigator, stackConfig.containerOptions);
|
||||
};
|
||||
|
||||
@@ -12,24 +12,22 @@ import NavigatorTypes from './NavigatorTypes';
|
||||
import type { TabViewConfig } from '../views/TabView/TabView';
|
||||
|
||||
import type {
|
||||
NavigationContainerConfig,
|
||||
NavigationRouteConfigMap,
|
||||
NavigationTabRouterConfig,
|
||||
} from '../TypeDefinition';
|
||||
|
||||
export type TabNavigatorConfig =
|
||||
& { containerOptions?: void }
|
||||
& NavigationTabRouterConfig
|
||||
& TabViewConfig
|
||||
& NavigationContainerConfig;
|
||||
& TabViewConfig;
|
||||
|
||||
const TabNavigator = (
|
||||
routeConfigs: NavigationRouteConfigMap,
|
||||
config: TabNavigatorConfig = {}
|
||||
config: TabNavigatorConfig = {},
|
||||
) => {
|
||||
// Use the look native to the platform by default
|
||||
const mergedConfig = { ...TabNavigator.Presets.Default, ...config };
|
||||
const {
|
||||
containerOptions,
|
||||
tabBarComponent,
|
||||
tabBarPosition,
|
||||
tabBarOptions,
|
||||
@@ -38,18 +36,23 @@ const TabNavigator = (
|
||||
lazyLoad,
|
||||
...tabsConfig
|
||||
} = mergedConfig;
|
||||
|
||||
const router = TabRouter(routeConfigs, tabsConfig);
|
||||
return createNavigationContainer(createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)((props: *) =>
|
||||
<TabView
|
||||
{...props}
|
||||
tabBarComponent={tabBarComponent}
|
||||
tabBarPosition={tabBarPosition}
|
||||
tabBarOptions={tabBarOptions}
|
||||
swipeEnabled={swipeEnabled}
|
||||
animationEnabled={animationEnabled}
|
||||
lazyLoad={lazyLoad}
|
||||
/>
|
||||
), containerOptions);
|
||||
const navigator = createNavigator(router, routeConfigs, config, NavigatorTypes.STACK)(
|
||||
(props: *) => (
|
||||
<TabView
|
||||
{...props}
|
||||
tabBarComponent={tabBarComponent}
|
||||
tabBarPosition={tabBarPosition}
|
||||
tabBarOptions={tabBarOptions}
|
||||
swipeEnabled={swipeEnabled}
|
||||
animationEnabled={animationEnabled}
|
||||
lazyLoad={lazyLoad}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
return createNavigationContainer(navigator, tabsConfig.containerOptions);
|
||||
};
|
||||
|
||||
const Presets = {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
} from '../TypeDefinition';
|
||||
|
||||
import type {
|
||||
NavigatorType
|
||||
NavigatorType,
|
||||
} from './NavigatorTypes';
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ import type {
|
||||
const createNavigator = (router: NavigationRouter<*, *, *>, routeConfigs: NavigationRouteConfigMap, navigatorConfig: any, navigatorType: NavigatorType) =>
|
||||
(View: NavigationNavigator<*, *, *, *>) => {
|
||||
class Navigator extends React.Component {
|
||||
props: NavigationNavigatorProps;
|
||||
props: NavigationNavigatorProps<*>;
|
||||
|
||||
static router = router;
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@ import type {
|
||||
NavigationScreenProp,
|
||||
NavigationRoute,
|
||||
NavigationAction,
|
||||
NavigationNavigatorProps,
|
||||
} from '../TypeDefinition';
|
||||
|
||||
type Props = {
|
||||
screenProps?: {};
|
||||
navigation: NavigationScreenProp<NavigationRoute, NavigationAction>;
|
||||
navigationOptions: *,
|
||||
component: ReactClass<*>;
|
||||
component: ReactClass<NavigationNavigatorProps<NavigationRoute>>;
|
||||
};
|
||||
|
||||
export default class SceneView extends PureComponent<void, Props, void> {
|
||||
|
||||
Reference in New Issue
Block a user