mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-24 04:25:34 +08:00
refactor: simplify props for stack and drawer headers
BREAKING CHANGE: Previously, the stack header accepted scene and previous scene which contained things such as descriptor, navigation prop, progress etc. The commit simplifies them to pass `route`, `navigation`, `options` and `progress` directly to the header. Similaryly, the `previous` argument now contains `options`, `route` and `progress`.
This commit is contained in:
@@ -75,34 +75,32 @@ export type GestureDirection =
|
||||
| 'vertical'
|
||||
| 'vertical-inverted';
|
||||
|
||||
export type Scene<T> = {
|
||||
export type Scene = {
|
||||
/**
|
||||
* Current route object,
|
||||
*/
|
||||
route: T;
|
||||
/**
|
||||
* Descriptor object for the route containing options and navigation object.
|
||||
* Descriptor object for the screen.
|
||||
*/
|
||||
descriptor: StackDescriptor;
|
||||
/**
|
||||
* Animated nodes representing the progress of the animation.
|
||||
*/
|
||||
progress: {
|
||||
/**
|
||||
* Progress value of the current screen.
|
||||
*/
|
||||
current: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Progress value for the screen after this one in the stack.
|
||||
* This can be `undefined` in case the screen animating is the last one.
|
||||
*/
|
||||
next?: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Progress value for the screen before this one in the stack.
|
||||
* This can be `undefined` in case the screen animating is the first one.
|
||||
*/
|
||||
previous?: Animated.AnimatedInterpolation;
|
||||
};
|
||||
progress: SceneProgress;
|
||||
};
|
||||
|
||||
export type SceneProgress = {
|
||||
/**
|
||||
* Progress value of the current screen.
|
||||
*/
|
||||
current: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Progress value for the screen after this one in the stack.
|
||||
* This can be `undefined` in case the screen animating is the last one.
|
||||
*/
|
||||
next?: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Progress value for the screen before this one in the stack.
|
||||
* This can be `undefined` in case the screen animating is the first one.
|
||||
*/
|
||||
previous?: Animated.AnimatedInterpolation;
|
||||
};
|
||||
|
||||
export type StackHeaderMode = 'float' | 'screen';
|
||||
@@ -221,11 +219,6 @@ export type StackHeaderOptions = {
|
||||
};
|
||||
|
||||
export type StackHeaderProps = {
|
||||
/**
|
||||
* Mode of the header: `float` renders a single floating header across all screens,
|
||||
* `screen` renders separate headers for each screen.
|
||||
*/
|
||||
mode: 'float' | 'screen';
|
||||
/**
|
||||
* Layout of the screen.
|
||||
*/
|
||||
@@ -234,14 +227,35 @@ export type StackHeaderProps = {
|
||||
* Safe area insets to use in the header, e.g. to apply extra spacing for statusbar and notch.
|
||||
*/
|
||||
insets: EdgeInsets;
|
||||
/**
|
||||
* Object representing the current scene, such as the route object and animation progress.
|
||||
*/
|
||||
scene: Scene<Route<string>>;
|
||||
/**
|
||||
* Object representing the previous scene.
|
||||
*/
|
||||
previous?: Scene<Route<string>>;
|
||||
previous?: {
|
||||
/**
|
||||
* Options for the previous screen.
|
||||
*/
|
||||
options: StackNavigationOptions;
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
route: Route<string>;
|
||||
/**
|
||||
* Animated nodes representing the progress of the animation of the previous screen.
|
||||
*/
|
||||
progress: SceneProgress;
|
||||
};
|
||||
/**
|
||||
* Animated nodes representing the progress of the animation.
|
||||
*/
|
||||
progress: SceneProgress;
|
||||
/**
|
||||
* Options for the current screen.
|
||||
*/
|
||||
options: StackNavigationOptions;
|
||||
/**
|
||||
* Route object for the current screen.
|
||||
*/
|
||||
route: Route<string>;
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import type { Route } from '@react-navigation/native';
|
||||
import type { Scene } from '../types';
|
||||
|
||||
const PreviousSceneContext = React.createContext<
|
||||
Scene<Route<string>> | undefined
|
||||
>(undefined);
|
||||
const PreviousSceneContext = React.createContext<Scene | undefined>(undefined);
|
||||
|
||||
export default PreviousSceneContext;
|
||||
|
||||
@@ -9,21 +9,22 @@ import debounce from '../../utils/debounce';
|
||||
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';
|
||||
|
||||
export default React.memo(function Header({
|
||||
scene,
|
||||
previous,
|
||||
layout,
|
||||
insets,
|
||||
progress,
|
||||
options,
|
||||
route,
|
||||
navigation,
|
||||
styleInterpolator,
|
||||
}: StackHeaderProps) {
|
||||
const { options } = scene.descriptor;
|
||||
const title =
|
||||
typeof options.headerTitle !== 'function' &&
|
||||
options.headerTitle !== undefined
|
||||
? options.headerTitle
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: scene.route.name;
|
||||
: route.name;
|
||||
|
||||
let leftLabel;
|
||||
|
||||
@@ -32,7 +33,7 @@ export default React.memo(function Header({
|
||||
if (options.headerBackTitle !== undefined) {
|
||||
leftLabel = options.headerBackTitle;
|
||||
} else if (previous) {
|
||||
const o = previous.descriptor.options;
|
||||
const o = previous.options;
|
||||
|
||||
leftLabel =
|
||||
typeof o.headerTitle !== 'function' && o.headerTitle !== undefined
|
||||
@@ -48,17 +49,17 @@ export default React.memo(function Header({
|
||||
if (navigation.isFocused() && navigation.canGoBack()) {
|
||||
navigation.dispatch({
|
||||
...StackActions.pop(),
|
||||
source: scene.route.key,
|
||||
source: route.key,
|
||||
});
|
||||
}
|
||||
}, 50),
|
||||
[navigation, scene.route.key]
|
||||
[navigation, route.key]
|
||||
);
|
||||
|
||||
const isModal = React.useContext(ModalPresentationContext);
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
(state) => state.routes[0].key === scene.route.key
|
||||
(state) => state.routes[0].key === route.key
|
||||
);
|
||||
|
||||
const statusBarHeight =
|
||||
@@ -67,9 +68,9 @@ export default React.memo(function Header({
|
||||
return (
|
||||
<HeaderSegment
|
||||
{...options}
|
||||
progress={progress}
|
||||
insets={insets}
|
||||
layout={layout}
|
||||
scene={scene}
|
||||
title={title}
|
||||
leftLabel={leftLabel}
|
||||
headerTitle={
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
Scene,
|
||||
StackHeaderStyleInterpolator,
|
||||
StackNavigationProp,
|
||||
StackHeaderProps,
|
||||
GestureDirection,
|
||||
} from '../../types';
|
||||
|
||||
@@ -28,10 +29,8 @@ export type Props = {
|
||||
mode: 'float' | 'screen';
|
||||
layout: Layout;
|
||||
insets: EdgeInsets;
|
||||
scenes: (Scene<Route<string>> | undefined)[];
|
||||
getPreviousScene: (props: {
|
||||
route: Route<string>;
|
||||
}) => Scene<Route<string>> | undefined;
|
||||
scenes: (Scene | undefined)[];
|
||||
getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
|
||||
getFocusedRoute: () => Route<string>;
|
||||
onContentHeightChange?: (props: {
|
||||
route: Route<string>;
|
||||
@@ -71,34 +70,42 @@ export default function HeaderContainer({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFocused = focusedRoute.key === scene.route.key;
|
||||
const previous =
|
||||
getPreviousScene({ route: scene.route }) ?? parentPreviousScene;
|
||||
const isFocused = focusedRoute.key === scene.descriptor.route.key;
|
||||
const previousScene =
|
||||
getPreviousScene({ route: scene.descriptor.route }) ??
|
||||
parentPreviousScene;
|
||||
|
||||
// If the screen is next to a headerless screen, we need to make the header appear static
|
||||
// This makes the header look like it's moving with the screen
|
||||
const previousScene = self[i - 1];
|
||||
const nextScene = self[i + 1];
|
||||
const previousDescriptor = self[i - 1]?.descriptor;
|
||||
const nextDescriptor = self[i + 1]?.descriptor;
|
||||
|
||||
const { headerShown: previousHeaderShown = true } =
|
||||
previousScene?.descriptor.options || {};
|
||||
previousDescriptor?.options || {};
|
||||
|
||||
const { headerShown: nextHeaderShown = true } =
|
||||
nextScene?.descriptor.options || {};
|
||||
nextDescriptor?.options || {};
|
||||
|
||||
const isHeaderStatic =
|
||||
(previousHeaderShown === false &&
|
||||
// We still need to animate when coming back from next scene
|
||||
// A hacky way to check this is if the next scene exists
|
||||
!nextScene) ||
|
||||
!nextDescriptor) ||
|
||||
nextHeaderShown === false;
|
||||
|
||||
const props = {
|
||||
mode,
|
||||
const props: StackHeaderProps = {
|
||||
layout,
|
||||
insets,
|
||||
scene,
|
||||
previous,
|
||||
previous: previousScene
|
||||
? {
|
||||
progress: previousScene.progress,
|
||||
options: previousScene.descriptor.options,
|
||||
route: previousScene.descriptor.route,
|
||||
}
|
||||
: undefined,
|
||||
progress: scene.progress,
|
||||
options: scene.descriptor.options,
|
||||
route: scene.descriptor.route,
|
||||
navigation: scene.descriptor.navigation as StackNavigationProp<
|
||||
ParamListBase
|
||||
>,
|
||||
@@ -117,10 +124,10 @@ export default function HeaderContainer({
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
key={scene.route.key}
|
||||
key={scene.descriptor.route.key}
|
||||
value={scene.descriptor.navigation}
|
||||
>
|
||||
<NavigationRouteContext.Provider value={scene.route}>
|
||||
<NavigationRouteContext.Provider value={scene.descriptor.route}>
|
||||
<View
|
||||
onLayout={
|
||||
onContentHeightChange
|
||||
@@ -128,7 +135,7 @@ export default function HeaderContainer({
|
||||
const { height } = e.nativeEvent.layout;
|
||||
|
||||
onContentHeightChange({
|
||||
route: scene.route,
|
||||
route: scene.descriptor.route,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { Route } from '@react-navigation/native';
|
||||
import HeaderBackButton from './HeaderBackButton';
|
||||
import HeaderBackground from './HeaderBackground';
|
||||
import memoize from '../../utils/memoize';
|
||||
@@ -18,7 +17,7 @@ import type {
|
||||
StackHeaderLeftButtonProps,
|
||||
StackHeaderTitleProps,
|
||||
StackHeaderOptions,
|
||||
Scene,
|
||||
SceneProgress,
|
||||
} from '../../types';
|
||||
|
||||
type Props = Omit<
|
||||
@@ -32,7 +31,7 @@ type Props = Omit<
|
||||
onGoBack?: () => void;
|
||||
title?: string;
|
||||
leftLabel?: string;
|
||||
scene: Scene<Route<string>>;
|
||||
progress: SceneProgress;
|
||||
styleInterpolator: StackHeaderStyleInterpolator;
|
||||
};
|
||||
|
||||
@@ -140,7 +139,7 @@ export default function HeaderSegment(props: Props) {
|
||||
);
|
||||
|
||||
const {
|
||||
scene,
|
||||
progress,
|
||||
layout,
|
||||
insets,
|
||||
title: currentTitle,
|
||||
@@ -281,8 +280,8 @@ export default function HeaderSegment(props: Props) {
|
||||
} = getInterpolatedStyle(
|
||||
styleInterpolator,
|
||||
layout,
|
||||
scene.progress.current,
|
||||
scene.progress.next,
|
||||
progress.current,
|
||||
progress.next,
|
||||
titleLayout,
|
||||
previousTitle ? leftLabelLayout : undefined,
|
||||
typeof height === 'number' ? height : defaultHeight
|
||||
|
||||
@@ -9,11 +9,11 @@ import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import PreviousSceneContext from '../../utils/PreviousSceneContext';
|
||||
import ModalPresentationContext from '../../utils/ModalPresentationContext';
|
||||
import type {
|
||||
Scene,
|
||||
Layout,
|
||||
StackHeaderMode,
|
||||
StackCardMode,
|
||||
TransitionPreset,
|
||||
Scene,
|
||||
} from '../../types';
|
||||
|
||||
type Props = TransitionPreset & {
|
||||
@@ -23,7 +23,7 @@ type Props = TransitionPreset & {
|
||||
closing: boolean;
|
||||
layout: Layout;
|
||||
gesture: Animated.Value;
|
||||
scene: Scene<Route<string>>;
|
||||
scene: Scene;
|
||||
safeAreaInsetTop: number;
|
||||
safeAreaInsetRight: number;
|
||||
safeAreaInsetBottom: number;
|
||||
@@ -34,9 +34,7 @@ type Props = TransitionPreset & {
|
||||
cardOverlayEnabled: boolean;
|
||||
cardShadowEnabled?: boolean;
|
||||
cardStyle?: StyleProp<ViewStyle>;
|
||||
getPreviousScene: (props: {
|
||||
route: Route<string>;
|
||||
}) => Scene<Route<string>> | undefined;
|
||||
getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
|
||||
getFocusedRoute: () => Route<string>;
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
renderScene: (props: { route: Route<string> }) => React.ReactNode;
|
||||
@@ -123,27 +121,27 @@ function CardContainer({
|
||||
}, [active, onPageChangeConfirm]);
|
||||
|
||||
const handleOpen = () => {
|
||||
onTransitionEnd?.({ route: scene.route }, false);
|
||||
onOpenRoute({ route: scene.route });
|
||||
onTransitionEnd?.({ route: scene.descriptor.route }, false);
|
||||
onOpenRoute({ route: scene.descriptor.route });
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onTransitionEnd?.({ route: scene.route }, true);
|
||||
onCloseRoute({ route: scene.route });
|
||||
onTransitionEnd?.({ route: scene.descriptor.route }, true);
|
||||
onCloseRoute({ route: scene.descriptor.route });
|
||||
};
|
||||
|
||||
const handleGestureBegin = () => {
|
||||
onPageChangeStart?.();
|
||||
onGestureStart?.({ route: scene.route });
|
||||
onGestureStart?.({ route: scene.descriptor.route });
|
||||
};
|
||||
|
||||
const handleGestureCanceled = () => {
|
||||
onPageChangeCancel?.();
|
||||
onGestureCancel?.({ route: scene.route });
|
||||
onGestureCancel?.({ route: scene.descriptor.route });
|
||||
};
|
||||
|
||||
const handleGestureEnd = () => {
|
||||
onGestureEnd?.({ route: scene.route });
|
||||
onGestureEnd?.({ route: scene.descriptor.route });
|
||||
};
|
||||
|
||||
const handleTransitionStart = ({ closing }: { closing: boolean }) => {
|
||||
@@ -153,7 +151,7 @@ function CardContainer({
|
||||
onPageChangeCancel?.();
|
||||
}
|
||||
|
||||
onTransitionStart?.({ route: scene.route }, closing);
|
||||
onTransitionStart?.({ route: scene.descriptor.route }, closing);
|
||||
};
|
||||
|
||||
const insets = {
|
||||
@@ -185,7 +183,7 @@ function CardContainer({
|
||||
};
|
||||
}, [pointerEvents, scene.progress.next]);
|
||||
|
||||
const previousScene = getPreviousScene({ route: scene.route });
|
||||
const previousScene = getPreviousScene({ route: scene.descriptor.route });
|
||||
const isModalPresentation = cardStyleInterpolator === forModalPresentationIOS;
|
||||
|
||||
return (
|
||||
@@ -234,7 +232,7 @@ function CardContainer({
|
||||
value={isParentHeaderShown || headerShown !== false}
|
||||
>
|
||||
<HeaderHeightContext.Provider value={headerHeight}>
|
||||
{renderScene({ route: scene.route })}
|
||||
{renderScene({ route: scene.descriptor.route })}
|
||||
</HeaderHeightContext.Provider>
|
||||
</HeaderShownContext.Provider>
|
||||
</PreviousSceneContext.Provider>
|
||||
|
||||
@@ -32,10 +32,10 @@ import type {
|
||||
Layout,
|
||||
StackHeaderMode,
|
||||
StackCardMode,
|
||||
Scene,
|
||||
StackDescriptorMap,
|
||||
StackNavigationOptions,
|
||||
StackDescriptor,
|
||||
Scene,
|
||||
} from '../../types';
|
||||
|
||||
type GestureValues = {
|
||||
@@ -77,7 +77,7 @@ type Props = {
|
||||
type State = {
|
||||
routes: Route<string>[];
|
||||
descriptors: StackDescriptorMap;
|
||||
scenes: Scene<Route<string>>[];
|
||||
scenes: Scene[];
|
||||
gestures: GestureValues;
|
||||
layout: Layout;
|
||||
headerHeights: Record<string, number>;
|
||||
@@ -248,7 +248,6 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
: undefined,
|
||||
},
|
||||
__memo: [
|
||||
route,
|
||||
state.layout,
|
||||
descriptor,
|
||||
nextDescriptor,
|
||||
@@ -365,7 +364,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
|
||||
if (previousRoute) {
|
||||
const previousScene = scenes.find(
|
||||
(scene) => scene.route.key === previousRoute.key
|
||||
(scene) => scene.descriptor.route.key === previousRoute.key
|
||||
);
|
||||
|
||||
return previousScene;
|
||||
|
||||
Reference in New Issue
Block a user