mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-03-06 17:34:59 +08:00
feat: add canGoBack (#50)
This commit is contained in:
@@ -78,6 +78,10 @@ const BaseRouter = {
|
||||
shouldActionChangeFocus(action: CommonAction) {
|
||||
return action.type === 'NAVIGATE';
|
||||
},
|
||||
|
||||
canGoBack() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export default BaseRouter;
|
||||
|
||||
@@ -88,9 +88,7 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
|
||||
const dispatch = (action: NavigationAction) =>
|
||||
context.performTransaction(() => {
|
||||
for (let i = 0; i < actionListeners.length; i++) {
|
||||
actionListeners[i](action, undefined, null);
|
||||
}
|
||||
actionListeners[0](action, undefined, null);
|
||||
});
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
|
||||
@@ -97,7 +97,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
return false;
|
||||
},
|
||||
|
||||
actionCreators: {},
|
||||
canGoBack() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
return router;
|
||||
|
||||
@@ -3,8 +3,11 @@ import { render, act } from 'react-native-testing-library';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import NavigationContainer from '../NavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
import { NavigationState } from '../types';
|
||||
import MockRouter, {
|
||||
MockActions,
|
||||
MockRouterKey,
|
||||
} from './__fixtures__/MockRouter';
|
||||
import { DefaultRouterOptions, NavigationState, Router } from '../types';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
@@ -54,6 +57,180 @@ it('sets options with options prop as an object', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it("returns correct value for canGoBack when it's not overridden", () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
const { render, options } = descriptors[state.routes[state.index].key];
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>{options.title}</h1>
|
||||
<div>{render()}</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
let result = true;
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
React.useEffect(() => {
|
||||
result = navigation.canGoBack();
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = (
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen
|
||||
name="foo"
|
||||
component={TestScreen}
|
||||
options={{ title: 'Hello world' }}
|
||||
/>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(root).update(root);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it("returns correct value for canGoBack when it's overridden", () => {
|
||||
function ParentRouter(options: DefaultRouterOptions) {
|
||||
const CurrentMockRouter = MockRouter(options);
|
||||
const ChildRouter: Router<NavigationState, MockActions> = {
|
||||
...CurrentMockRouter,
|
||||
|
||||
canGoBack() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return ChildRouter;
|
||||
}
|
||||
|
||||
const ParentNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
{ title?: string },
|
||||
any
|
||||
>(ParentRouter, props);
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const ChildNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
let result = false;
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
React.useEffect(() => {
|
||||
result = navigation.canGoBack();
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = (
|
||||
<NavigationContainer>
|
||||
<ParentNavigator>
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<ChildNavigator>
|
||||
<Screen name="qux" component={TestScreen} />
|
||||
</ChildNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</ParentNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(root).update(root);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('returns correct value for canGoBack when parent router overrides it', () => {
|
||||
function OverrodeRouter(options: DefaultRouterOptions) {
|
||||
const CurrentMockRouter = MockRouter(options);
|
||||
const ChildRouter: Router<NavigationState, MockActions> = {
|
||||
...CurrentMockRouter,
|
||||
|
||||
canGoBack() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return ChildRouter;
|
||||
}
|
||||
|
||||
const OverrodeNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
{ title?: string },
|
||||
any
|
||||
>(OverrodeRouter, props);
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
NavigationState,
|
||||
{ title?: string },
|
||||
any
|
||||
>(MockRouter, props);
|
||||
|
||||
return descriptors[state.routes[state.index].key].render();
|
||||
};
|
||||
|
||||
let result = true;
|
||||
|
||||
const TestScreen = ({ navigation }: any): any => {
|
||||
React.useEffect(() => {
|
||||
result = navigation.canGoBack();
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = (
|
||||
<NavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="baz">
|
||||
{() => (
|
||||
<TestNavigator>
|
||||
<Screen name="qux" component={TestScreen} />
|
||||
</TestNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
<Screen name="qux">
|
||||
{() => (
|
||||
<OverrodeNavigator>
|
||||
<Screen name="qux" component={() => null} />
|
||||
</OverrodeNavigator>
|
||||
)}
|
||||
</Screen>
|
||||
</TestNavigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
render(root).update(root);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('sets options with options prop as a fuction', () => {
|
||||
const TestNavigator = (props: any) => {
|
||||
const { state, descriptors } = useNavigationBuilder<
|
||||
|
||||
@@ -154,6 +154,13 @@ export type Router<
|
||||
*/
|
||||
shouldActionChangeFocus(action: NavigationAction): boolean;
|
||||
|
||||
/**
|
||||
* Whether the back action will be handled by navigation
|
||||
*
|
||||
* @param state State object to check.
|
||||
*/
|
||||
canGoBack(state: State): boolean;
|
||||
|
||||
/**
|
||||
* Action creators for the router.
|
||||
*/
|
||||
@@ -264,6 +271,12 @@ type NavigationHelpersCommon<
|
||||
* To get notified of focus changes, use `addListener('focus', cb)` and `addListener('blur', cb)`.
|
||||
*/
|
||||
isFocused(): boolean;
|
||||
|
||||
/**
|
||||
* Check if dispatching back action will be handled by navigation.
|
||||
* Note that this method doesn't re-render screen when the result changes. So don't use it in `render`.
|
||||
*/
|
||||
canGoBack(): boolean;
|
||||
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
|
||||
|
||||
export type NavigationHelpers<
|
||||
|
||||
@@ -198,6 +198,7 @@ export default function useNavigationBuilder<
|
||||
getState,
|
||||
setState,
|
||||
emitter,
|
||||
router,
|
||||
actionCreators: router.actionCreators,
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NavigationState,
|
||||
ActionCreators,
|
||||
ParamListBase,
|
||||
Router,
|
||||
} from './types';
|
||||
|
||||
type Options<Action extends NavigationAction> = {
|
||||
@@ -21,6 +22,7 @@ type Options<Action extends NavigationAction> = {
|
||||
setState: (state: NavigationState) => void;
|
||||
actionCreators?: ActionCreators<Action>;
|
||||
emitter: NavigationEventEmitter;
|
||||
router: Router<NavigationState, Action>;
|
||||
};
|
||||
|
||||
export default function useNavigationHelpers<Action extends NavigationAction>({
|
||||
@@ -29,6 +31,7 @@ export default function useNavigationHelpers<Action extends NavigationAction>({
|
||||
setState,
|
||||
actionCreators,
|
||||
emitter,
|
||||
router,
|
||||
}: Options<Action>) {
|
||||
const parentNavigationHelpers = React.useContext(NavigationContext);
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
@@ -68,14 +71,19 @@ export default function useNavigationHelpers<Action extends NavigationAction>({
|
||||
isFocused: parentNavigationHelpers
|
||||
? parentNavigationHelpers.isFocused
|
||||
: () => true,
|
||||
canGoBack: () =>
|
||||
router.canGoBack(getState()) ||
|
||||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
|
||||
false,
|
||||
};
|
||||
}, [
|
||||
actionCreators,
|
||||
router,
|
||||
getState,
|
||||
parentNavigationHelpers,
|
||||
emitter.emit,
|
||||
performTransaction,
|
||||
setState,
|
||||
getState,
|
||||
onAction,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -225,6 +225,10 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
}
|
||||
},
|
||||
|
||||
canGoBack(state) {
|
||||
return state.routes.length > 1;
|
||||
},
|
||||
|
||||
actionCreators: StackActions,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user