mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-06 22:39:41 +08:00
refactor: split useNavigationBuilder into more hooks
This commit is contained in:
9
src/NavigationBuilderContext.tsx
Normal file
9
src/NavigationBuilderContext.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationHelpers, NavigationAction } from './types';
|
||||
|
||||
const NavigationBuilderContext = React.createContext<{
|
||||
helpers?: NavigationHelpers;
|
||||
onAction?: (action: NavigationAction) => boolean;
|
||||
}>({});
|
||||
|
||||
export default NavigationBuilderContext;
|
||||
@@ -46,6 +46,10 @@ export type NavigationAction = {
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type ActionCreators<Action extends NavigationAction = CommonAction> = {
|
||||
[key: string]: (...args: any) => Action;
|
||||
};
|
||||
|
||||
export type Router<Action extends NavigationAction = CommonAction> = {
|
||||
/**
|
||||
* Initialize full navigation state with a given partial state.
|
||||
@@ -68,7 +72,7 @@ export type Router<Action extends NavigationAction = CommonAction> = {
|
||||
/**
|
||||
* Action creators for the router.
|
||||
*/
|
||||
actionCreators: { [key: string]: (...args: any) => Action };
|
||||
actionCreators: ActionCreators<Action>;
|
||||
};
|
||||
|
||||
export type ParamListBase = { [key: string]: object | void };
|
||||
|
||||
63
src/useDescriptors.tsx
Normal file
63
src/useDescriptors.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Descriptor,
|
||||
InitialState,
|
||||
NavigationAction,
|
||||
NavigationHelpers,
|
||||
NavigationState,
|
||||
ParamListBase,
|
||||
ScreenProps,
|
||||
} from './types';
|
||||
import SceneView from './SceneView';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
|
||||
type Options = {
|
||||
state: NavigationState | InitialState;
|
||||
screens: { [key: string]: ScreenProps<ParamListBase, string> };
|
||||
helpers: NavigationHelpers<ParamListBase>;
|
||||
onAction: (action: NavigationAction) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
};
|
||||
|
||||
export default function useDescriptors({
|
||||
state,
|
||||
screens,
|
||||
helpers,
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
}: Options) {
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
helpers,
|
||||
onAction,
|
||||
}),
|
||||
[helpers, onAction]
|
||||
);
|
||||
|
||||
return state.routes.reduce(
|
||||
(acc, route) => {
|
||||
const screen = screens[route.name];
|
||||
|
||||
acc[route.key] = {
|
||||
render() {
|
||||
return (
|
||||
<NavigationBuilderContext.Provider value={context}>
|
||||
<SceneView
|
||||
helpers={helpers}
|
||||
route={route}
|
||||
screen={screen}
|
||||
getState={getState}
|
||||
setState={setState}
|
||||
/>
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
},
|
||||
options: screen.options || {},
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: Descriptor }
|
||||
);
|
||||
}
|
||||
@@ -1,51 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationStateContext } from './NavigationContainer';
|
||||
import {
|
||||
Router,
|
||||
NavigationAction,
|
||||
Descriptor,
|
||||
NavigationHelpers,
|
||||
NavigationState,
|
||||
ScreenProps,
|
||||
} from './types';
|
||||
import Screen from './Screen';
|
||||
import shortid from 'shortid';
|
||||
import SceneView from './SceneView';
|
||||
import * as BaseActions from './BaseActions';
|
||||
import { SingleNavigatorContext } from './EnsureSingleNavigator';
|
||||
import useRegisterNavigator from './useRegisterNavigator';
|
||||
import useDescriptors from './useDescriptors';
|
||||
import useNavigationHelpers from './useNavigationHelpers';
|
||||
import useOnAction from './useOnAction';
|
||||
import { Router, NavigationState, ScreenProps } from './types';
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
type HandleAction = (action: NavigationAction) => boolean;
|
||||
|
||||
const NavigationBuilderContext = React.createContext<{
|
||||
helpers?: NavigationHelpers;
|
||||
onAction?: HandleAction;
|
||||
}>({});
|
||||
|
||||
export default function useNavigationBuilder(
|
||||
router: Router<any>,
|
||||
options: Options
|
||||
) {
|
||||
const [key] = React.useState(shortid());
|
||||
const singleNavigatorContext = React.useContext(SingleNavigatorContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (singleNavigatorContext === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't register the navigator. You likely forgot to nest the navigator inside a 'NavigationContainer'."
|
||||
);
|
||||
}
|
||||
|
||||
const { register, unregister } = singleNavigatorContext;
|
||||
|
||||
register(key);
|
||||
|
||||
return () => unregister(key);
|
||||
}, [key, singleNavigatorContext]);
|
||||
useRegisterNavigator();
|
||||
|
||||
const screens = React.Children.map(options.children, child => {
|
||||
if (React.isValidElement(child) && child.type === Screen) {
|
||||
@@ -74,11 +45,6 @@ export default function useNavigationBuilder(
|
||||
setState,
|
||||
} = React.useContext(NavigationStateContext);
|
||||
|
||||
const {
|
||||
helpers: parentNavigationHelpers,
|
||||
onAction: handleActionParent,
|
||||
} = React.useContext(NavigationBuilderContext);
|
||||
|
||||
const getState = React.useCallback(
|
||||
(): NavigationState =>
|
||||
router.getInitialState({
|
||||
@@ -90,62 +56,18 @@ export default function useNavigationBuilder(
|
||||
[getCurrentState, ...routeNames]
|
||||
);
|
||||
|
||||
const onAction = React.useCallback(
|
||||
(action: NavigationAction) => {
|
||||
const state = getState();
|
||||
const onAction = useOnAction({
|
||||
getState,
|
||||
setState,
|
||||
getStateForAction: router.getStateForAction,
|
||||
});
|
||||
|
||||
const result = router.getStateForAction(state, action);
|
||||
|
||||
if (result !== null) {
|
||||
if (state !== result) {
|
||||
setState(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (handleActionParent !== undefined) {
|
||||
// Bubble action to the parent if the current navigator didn't handle it
|
||||
if (handleActionParent(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[getState, handleActionParent, router, setState]
|
||||
);
|
||||
|
||||
const helpers = React.useMemo((): NavigationHelpers => {
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationState)
|
||||
) => {
|
||||
if (typeof action === 'function') {
|
||||
setState(action(getState()));
|
||||
} else {
|
||||
onAction(action);
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
...router.actionCreators,
|
||||
...BaseActions,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
return {
|
||||
...parentNavigationHelpers,
|
||||
...Object.keys(actions).reduce(
|
||||
(acc, name) => {
|
||||
// @ts-ignore
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: () => void }
|
||||
),
|
||||
dispatch,
|
||||
};
|
||||
}, [getState, onAction, parentNavigationHelpers, router, setState]);
|
||||
const helpers = useNavigationHelpers({
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
actionCreators: router.actionCreators,
|
||||
});
|
||||
|
||||
const navigation = React.useMemo(
|
||||
() => ({
|
||||
@@ -155,38 +77,14 @@ export default function useNavigationBuilder(
|
||||
[helpers, currentState]
|
||||
);
|
||||
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
helpers,
|
||||
onAction,
|
||||
}),
|
||||
[helpers, onAction]
|
||||
);
|
||||
|
||||
const descriptors = currentState.routes.reduce(
|
||||
(acc, route) => {
|
||||
const screen = screens[route.name];
|
||||
|
||||
acc[route.key] = {
|
||||
render() {
|
||||
return (
|
||||
<NavigationBuilderContext.Provider value={context}>
|
||||
<SceneView
|
||||
helpers={helpers}
|
||||
route={route}
|
||||
screen={screen}
|
||||
getState={getState}
|
||||
setState={setState}
|
||||
/>
|
||||
</NavigationBuilderContext.Provider>
|
||||
);
|
||||
},
|
||||
options: screen.options || {},
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: Descriptor }
|
||||
);
|
||||
const descriptors = useDescriptors({
|
||||
state: currentState,
|
||||
screens,
|
||||
helpers,
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
});
|
||||
|
||||
return {
|
||||
navigation,
|
||||
|
||||
58
src/useNavigationHelpers.tsx
Normal file
58
src/useNavigationHelpers.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import * as React from 'react';
|
||||
import * as BaseActions from './BaseActions';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import {
|
||||
NavigationHelpers,
|
||||
NavigationAction,
|
||||
NavigationState,
|
||||
ActionCreators,
|
||||
} from './types';
|
||||
|
||||
type Options = {
|
||||
onAction: (action: NavigationAction) => boolean;
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
actionCreators: ActionCreators;
|
||||
};
|
||||
|
||||
export default function useNavigationHelpers({
|
||||
onAction,
|
||||
getState,
|
||||
setState,
|
||||
actionCreators,
|
||||
}: Options) {
|
||||
const { helpers: parentNavigationHelpers } = React.useContext(
|
||||
NavigationBuilderContext
|
||||
);
|
||||
|
||||
return React.useMemo((): NavigationHelpers => {
|
||||
const dispatch = (
|
||||
action: NavigationAction | ((state: NavigationState) => NavigationState)
|
||||
) => {
|
||||
if (typeof action === 'function') {
|
||||
setState(action(getState()));
|
||||
} else {
|
||||
onAction(action);
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
...actionCreators,
|
||||
...BaseActions,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
return {
|
||||
...parentNavigationHelpers,
|
||||
...Object.keys(actions).reduce(
|
||||
(acc, name) => {
|
||||
// @ts-ignore
|
||||
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: () => void }
|
||||
),
|
||||
dispatch,
|
||||
};
|
||||
}, [getState, onAction, parentNavigationHelpers, actionCreators, setState]);
|
||||
}
|
||||
48
src/useOnAction.tsx
Normal file
48
src/useOnAction.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||
import { NavigationAction, NavigationState } from './types';
|
||||
|
||||
type Options = {
|
||||
getState: () => NavigationState;
|
||||
setState: (state: NavigationState) => void;
|
||||
getStateForAction: (
|
||||
state: NavigationState,
|
||||
action: NavigationAction
|
||||
) => NavigationState | null;
|
||||
};
|
||||
|
||||
export default function useOnAction({
|
||||
getState,
|
||||
setState,
|
||||
getStateForAction,
|
||||
}: Options) {
|
||||
const { onAction: handleActionParent } = React.useContext(
|
||||
NavigationBuilderContext
|
||||
);
|
||||
|
||||
return React.useCallback(
|
||||
(action: NavigationAction) => {
|
||||
const state = getState();
|
||||
|
||||
const result = getStateForAction(state, action);
|
||||
|
||||
if (result !== null) {
|
||||
if (state !== result) {
|
||||
setState(result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (handleActionParent !== undefined) {
|
||||
// Bubble action to the parent if the current navigator didn't handle it
|
||||
if (handleActionParent(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[getState, handleActionParent, getStateForAction, setState]
|
||||
);
|
||||
}
|
||||
22
src/useRegisterNavigator.tsx
Normal file
22
src/useRegisterNavigator.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import shortid from 'shortid';
|
||||
import { SingleNavigatorContext } from './EnsureSingleNavigator';
|
||||
|
||||
export default function useRegisterNavigator() {
|
||||
const [key] = React.useState(shortid());
|
||||
const singleNavigatorContext = React.useContext(SingleNavigatorContext);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (singleNavigatorContext === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't register the navigator. You likely forgot to nest the navigator inside a 'NavigationContainer'."
|
||||
);
|
||||
}
|
||||
|
||||
const { register, unregister } = singleNavigatorContext;
|
||||
|
||||
register(key);
|
||||
|
||||
return () => unregister(key);
|
||||
}, [key, singleNavigatorContext]);
|
||||
}
|
||||
Reference in New Issue
Block a user