mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-11 17:30:51 +08:00
The `isFirstRouteInParent` method was added to determine whether the back button should be shown in the header for stack navigator or not. This was mainly due to the API of the old version of stack whose public API of header didn't have all required info to determine whether it should be shown. It was probably a mistake to add it, because this method doesn't look at history and so pretty much useless for other navigators which aren't stack. In the new stack, the header receives a `previous` prop and the `headerLeft` option receives a `canGoBack` prop which can be used for this purpose. It's also possible to check if a route is the first by doing `navigation.dangerouslyGetState().routes[0].key === route.key`. So it's time to drop this method.
132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import * as React from 'react';
|
|
import * as CommonActions from './CommonActions';
|
|
import { NavigationEventEmitter } from './useEventEmitter';
|
|
import NavigationContext from './NavigationContext';
|
|
|
|
import {
|
|
NavigationAction,
|
|
NavigationHelpers,
|
|
NavigationProp,
|
|
ParamListBase,
|
|
NavigationState,
|
|
Router,
|
|
} 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;
|
|
};
|
|
|
|
type NavigationCache<
|
|
State extends NavigationState,
|
|
ScreenOptions extends object
|
|
> = {
|
|
[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 object
|
|
>({
|
|
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 parentNavigation = React.useContext(NavigationContext);
|
|
|
|
const cache = React.useMemo(
|
|
() => ({ current: {} as NavigationCache<State, ScreenOptions> }),
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[getState, navigation, setOptions, router, emitter, parentNavigation]
|
|
);
|
|
|
|
const actions = {
|
|
...router.actionCreators,
|
|
...CommonActions,
|
|
};
|
|
|
|
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
|
(acc, route, index) => {
|
|
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-ignore
|
|
acc[name] = (...args: any) => dispatch(actions[name](...args));
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
|
|
acc[route.key] = {
|
|
...rest,
|
|
...helpers,
|
|
...emitter.create(route.key),
|
|
dangerouslyGetParent: () => parentNavigation as any,
|
|
dangerouslyGetState: getState,
|
|
dispatch,
|
|
setOptions: (options: object) =>
|
|
setOptions(o => ({
|
|
...o,
|
|
[route.key]: { ...o[route.key], ...options },
|
|
})),
|
|
isFocused: () => {
|
|
const state = getState();
|
|
|
|
if (index !== state.index) {
|
|
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;
|
|
}
|