mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-05-08 12:10:13 +08:00
126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
import * as React from 'react';
|
|
import {
|
|
CommonActions,
|
|
NavigationAction,
|
|
ParamListBase,
|
|
NavigationState,
|
|
Router,
|
|
} from '@react-navigation/routers';
|
|
import type { NavigationEventEmitter } from './useEventEmitter';
|
|
|
|
import type { EventMapBase, NavigationHelpers, NavigationProp } from './types';
|
|
|
|
type Options<State extends NavigationState> = {
|
|
state: State;
|
|
getState: () => State;
|
|
navigation: NavigationHelpers<ParamListBase> &
|
|
Partial<NavigationProp<ParamListBase, string, any, any, any>>;
|
|
setOptions: (
|
|
cb: (options: Record<string, object>) => Record<string, object>
|
|
) => void;
|
|
router: Router<State, NavigationAction>;
|
|
emitter: NavigationEventEmitter<EventMapBase>;
|
|
};
|
|
|
|
type NavigationCache<
|
|
State extends NavigationState,
|
|
ScreenOptions extends {}
|
|
> = {
|
|
[key: string]: NavigationProp<ParamListBase, string, State, ScreenOptions>;
|
|
};
|
|
|
|
/**
|
|
* Hook to cache navigation objects for each screen in the navigator.
|
|
* It's important to cache them to make sure navigation objects don't change between renders.
|
|
* This lets us apply optimizations like `React.memo` to minimize re-rendering screens.
|
|
*/
|
|
export default function useNavigationCache<
|
|
State extends NavigationState,
|
|
ScreenOptions extends {}
|
|
>({
|
|
state,
|
|
getState,
|
|
navigation,
|
|
setOptions,
|
|
router,
|
|
emitter,
|
|
}: Options<State>) {
|
|
// Cache object which holds navigation objects for each screen
|
|
// We use `React.useMemo` instead of `React.useRef` coz we want to invalidate it when deps change
|
|
// In reality, these deps will rarely change, if ever
|
|
const cache = React.useMemo(
|
|
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[getState, navigation, setOptions, router, emitter]
|
|
);
|
|
|
|
const actions = {
|
|
...router.actionCreators,
|
|
...CommonActions,
|
|
};
|
|
|
|
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
|
(acc, route) => {
|
|
const previous = cache.current[route.key];
|
|
|
|
if (previous) {
|
|
// If a cached navigation object already exists, reuse it
|
|
acc[route.key] = previous;
|
|
} else {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const { emit, ...rest } = navigation;
|
|
|
|
const dispatch = (
|
|
action: NavigationAction | ((state: State) => NavigationAction)
|
|
) => {
|
|
const payload =
|
|
typeof action === 'function' ? action(getState()) : action;
|
|
|
|
navigation.dispatch(
|
|
typeof payload === 'object' && payload != null
|
|
? { source: route.key, ...payload }
|
|
: payload
|
|
);
|
|
};
|
|
|
|
const helpers = Object.keys(actions).reduce<Record<string, () => void>>(
|
|
(acc, name) => {
|
|
// @ts-expect-error: name is a valid key, but TypeScript is dumb
|
|
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
|
|
acc[route.key] = {
|
|
...rest,
|
|
...helpers,
|
|
...emitter.create(route.key),
|
|
dispatch,
|
|
setOptions: (options: object) =>
|
|
setOptions((o) => ({
|
|
...o,
|
|
[route.key]: { ...o[route.key], ...options },
|
|
})),
|
|
isFocused: () => {
|
|
const state = getState();
|
|
|
|
if (state.routes[state.index].key !== route.key) {
|
|
return false;
|
|
}
|
|
|
|
// If the current screen is focused, we also need to check if parent navigator is focused
|
|
// This makes sure that we return the focus state in the whole tree, not just this navigator
|
|
return navigation ? navigation.isFocused() : true;
|
|
},
|
|
};
|
|
}
|
|
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
|
|
return cache.current;
|
|
}
|