mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-11 09:20:54 +08:00
feat: add typed navigator for better typechecking
This commit is contained in:
138
src/types.tsx
138
src/types.tsx
@@ -3,9 +3,21 @@ import * as BaseActions from './BaseActions';
|
||||
export type CommonAction = BaseActions.Action;
|
||||
|
||||
export type NavigationState = {
|
||||
/**
|
||||
* Unique key for the navigation state.
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* Index of the currently focused route.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
* List if valid route names.
|
||||
*/
|
||||
names: string[];
|
||||
/**
|
||||
* List of rendered routes.
|
||||
*/
|
||||
routes: Array<Route & { state?: NavigationState }>;
|
||||
};
|
||||
|
||||
@@ -15,10 +27,19 @@ export type InitialState = Omit<Omit<NavigationState, 'names'>, 'key'> & {
|
||||
state?: InitialState;
|
||||
};
|
||||
|
||||
export type Route = {
|
||||
export type Route<RouteName = string> = {
|
||||
/**
|
||||
* Unique key for the route.
|
||||
*/
|
||||
key: string;
|
||||
name: string;
|
||||
params?: object;
|
||||
/**
|
||||
* User-provided name for the route.
|
||||
*/
|
||||
name: RouteName;
|
||||
/**
|
||||
* Params for the route.
|
||||
*/
|
||||
params?: object | void;
|
||||
};
|
||||
|
||||
export type NavigationAction = {
|
||||
@@ -26,35 +47,70 @@ export type NavigationAction = {
|
||||
};
|
||||
|
||||
export type Router<Action extends NavigationAction = NavigationAction> = {
|
||||
normalize(options: {
|
||||
/**
|
||||
* Initialize full navigation state with a given partial state.
|
||||
*/
|
||||
initial(options: {
|
||||
screens: { [key: string]: ScreenProps };
|
||||
currentState?: NavigationState | InitialState;
|
||||
partialState?: NavigationState | InitialState;
|
||||
initialRouteName?: string;
|
||||
}): NavigationState;
|
||||
|
||||
/**
|
||||
* Take the current state and action, and return a new state.
|
||||
* If the action cannot be handled, return `null`.
|
||||
*/
|
||||
reduce(
|
||||
state: NavigationState,
|
||||
action: Action | CommonAction
|
||||
): NavigationState | null;
|
||||
|
||||
/**
|
||||
* Action creators for the router.
|
||||
*/
|
||||
actions: { [key: string]: (...args: any) => Action };
|
||||
};
|
||||
|
||||
export type ParamListBase = { [key: string]: object | void };
|
||||
|
||||
class PrivateValueStore<T> {
|
||||
// TypeScript requires a type to be actually used to be able to infer it.
|
||||
// This is a hacky way of storing type in a property without surfacing it in intellisense.
|
||||
/**
|
||||
* TypeScript requires a type to be actually used to be able to infer it.
|
||||
* This is a hacky way of storing type in a property without surfacing it in intellisense.
|
||||
*/
|
||||
// @ts-ignore
|
||||
private __private_value_type?: T;
|
||||
}
|
||||
|
||||
export type NavigationHelpers<ParamList extends ParamListBase = {}> = {
|
||||
export type NavigationHelpers<
|
||||
ParamList extends ParamListBase = ParamListBase
|
||||
> = {
|
||||
/**
|
||||
* Dispatch an action to the router.
|
||||
*/
|
||||
dispatch(action: NavigationAction): void;
|
||||
|
||||
/**
|
||||
* Navigate to a route in current navigation tree.
|
||||
*
|
||||
* @param name Name of the route to navigate to.
|
||||
* @param [params] Params object for the route.
|
||||
*/
|
||||
navigate<RouteName extends keyof ParamList>(
|
||||
...args: ParamList[RouteName] extends void
|
||||
? [RouteName]
|
||||
: [RouteName, ParamList[RouteName]]
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Reset the navigation state to the provided state.
|
||||
* If a key is provided, the state with matching key will be reset.
|
||||
*/
|
||||
reset(state: InitialState & { key?: string }): void;
|
||||
|
||||
/**
|
||||
* Go back to the previous route in history.
|
||||
*/
|
||||
goBack(): void;
|
||||
} & PrivateValueStore<ParamList>;
|
||||
|
||||
@@ -62,7 +118,11 @@ export type NavigationProp<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList
|
||||
> = NavigationHelpers<ParamList> & {
|
||||
state: Route & { name: RouteName } & (ParamList[RouteName] extends void
|
||||
/**
|
||||
* State for the child navigator.
|
||||
*/
|
||||
state: Route<RouteName> &
|
||||
(ParamList[RouteName] extends void
|
||||
? never
|
||||
: { params: ParamList[RouteName] });
|
||||
};
|
||||
@@ -77,19 +137,69 @@ export type CompositeNavigationProp<
|
||||
>;
|
||||
|
||||
export type Descriptor = {
|
||||
/**
|
||||
* Render the component associated with this route.
|
||||
*/
|
||||
render(): React.ReactNode;
|
||||
|
||||
/**
|
||||
* Options for the route.
|
||||
*/
|
||||
options: Options;
|
||||
};
|
||||
|
||||
export type Options = {
|
||||
/**
|
||||
* Title text for the screen.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type ScreenProps = {
|
||||
name: string;
|
||||
export type ScreenProps<
|
||||
ParamList extends ParamListBase = ParamListBase,
|
||||
RouteName extends keyof ParamList = string
|
||||
> = {
|
||||
/**
|
||||
* Route name of this screen.
|
||||
*/
|
||||
name: RouteName;
|
||||
|
||||
/**
|
||||
* Navigator options for this screen.
|
||||
*/
|
||||
options?: Options;
|
||||
initialParams?: object;
|
||||
|
||||
/**
|
||||
* Initial params object for the route.
|
||||
*/
|
||||
initialParams?: ParamList[RouteName];
|
||||
} & (
|
||||
| { component: React.ComponentType<any> }
|
||||
| { children: (props: any) => React.ReactNode });
|
||||
| {
|
||||
/**
|
||||
* React component to render for this screen.
|
||||
*/
|
||||
component: React.ComponentType<any>;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Render callback to render content of this screen.
|
||||
*/
|
||||
children: (props: any) => React.ReactNode;
|
||||
});
|
||||
|
||||
export type TypedNavigator<
|
||||
ParamList extends ParamListBase,
|
||||
Navigator extends React.ComponentType<any>
|
||||
> = {
|
||||
Navigator: React.ComponentType<
|
||||
React.ComponentProps<Navigator> & {
|
||||
/**
|
||||
* Route to focus on initial render.
|
||||
*/
|
||||
initialRouteName?: keyof ParamList;
|
||||
}
|
||||
>;
|
||||
Screen: React.ComponentType<ScreenProps<ParamList, keyof ParamList>>;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as BaseActions from './BaseActions';
|
||||
|
||||
type Options = {
|
||||
initialRouteName?: string;
|
||||
children: React.ReactElement[];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const NavigationHelpersContext = React.createContext<
|
||||
@@ -23,13 +23,13 @@ export const NavigationHelpersContext = React.createContext<
|
||||
|
||||
export default function useNavigationBuilder(router: Router, options: Options) {
|
||||
const screens = React.Children.map(options.children, child => {
|
||||
if (child.type !== Screen) {
|
||||
throw new Error(
|
||||
"A navigator can only contain 'Screen' components as its direct children"
|
||||
);
|
||||
if (React.isValidElement(child) && child.type === Screen) {
|
||||
return child.props as ScreenProps;
|
||||
}
|
||||
|
||||
return child.props as ScreenProps;
|
||||
throw new Error(
|
||||
"A navigator can only contain 'Screen' components as its direct children"
|
||||
);
|
||||
}).reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.name] = curr;
|
||||
@@ -41,7 +41,7 @@ export default function useNavigationBuilder(router: Router, options: Options) {
|
||||
const routeNames = Object.keys(screens);
|
||||
|
||||
const {
|
||||
state: currentState = router.normalize({
|
||||
state: currentState = router.initial({
|
||||
screens,
|
||||
initialRouteName: options.initialRouteName,
|
||||
}),
|
||||
@@ -51,9 +51,9 @@ export default function useNavigationBuilder(router: Router, options: Options) {
|
||||
|
||||
const getState = React.useCallback(
|
||||
(): NavigationState =>
|
||||
router.normalize({
|
||||
router.initial({
|
||||
screens,
|
||||
currentState: getCurrentState(),
|
||||
partialState: getCurrentState(),
|
||||
initialRouteName: options.initialRouteName,
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
Reference in New Issue
Block a user