mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-12 22:30:36 +08:00
Merge pull request #31 from slorber/switch-back-behavior
handle new SwitchRouter history back behaviors
This commit is contained in:
@@ -29,11 +29,18 @@ export default (routeConfigs, config = {}) => {
|
||||
const initialRouteParams = config.initialRouteParams;
|
||||
const initialRouteName = config.initialRouteName || order[0];
|
||||
const backBehavior = config.backBehavior || 'none';
|
||||
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
|
||||
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
||||
? config.resetOnBlur
|
||||
: true;
|
||||
|
||||
const initialRouteIndex = order.indexOf(initialRouteName);
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const childRouters = {};
|
||||
order.forEach(routeName => {
|
||||
childRouters[routeName] = null;
|
||||
@@ -57,13 +64,6 @@ export default (routeConfigs, config = {}) => {
|
||||
getActionForPathAndParams,
|
||||
} = createPathParser(childRouters, routeConfigs, config);
|
||||
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
function resetChildRoute(routeName) {
|
||||
let initialParams =
|
||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||
@@ -88,35 +88,56 @@ export default (routeConfigs, config = {}) => {
|
||||
};
|
||||
}
|
||||
|
||||
function getNextState(prevState, possibleNextState) {
|
||||
if (!prevState) {
|
||||
return possibleNextState;
|
||||
function getNextState(action, prevState, possibleNextState) {
|
||||
function updateNextStateHistory(nextState) {
|
||||
if (backBehavior !== 'history') {
|
||||
return nextState;
|
||||
}
|
||||
let nextRouteKeyHistory = prevState.routeKeyHistory;
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
nextRouteKeyHistory = [...prevState.routeKeyHistory]; // copy
|
||||
const keyToAdd = nextState.routes[nextState.index].key;
|
||||
nextRouteKeyHistory = nextRouteKeyHistory.filter(k => k !== keyToAdd); // dedup
|
||||
nextRouteKeyHistory.push(keyToAdd);
|
||||
} else if (action.type === NavigationActions.BACK) {
|
||||
nextRouteKeyHistory = [...prevState.routeKeyHistory]; // copy
|
||||
nextRouteKeyHistory.pop();
|
||||
}
|
||||
return {
|
||||
...nextState,
|
||||
routeKeyHistory: nextRouteKeyHistory,
|
||||
};
|
||||
}
|
||||
|
||||
let nextState;
|
||||
if (prevState.index !== possibleNextState.index && resetOnBlur) {
|
||||
let nextState = possibleNextState;
|
||||
if (
|
||||
prevState &&
|
||||
prevState.index !== possibleNextState.index &&
|
||||
resetOnBlur
|
||||
) {
|
||||
const prevRouteName = prevState.routes[prevState.index].routeName;
|
||||
const nextRoutes = [...possibleNextState.routes];
|
||||
nextRoutes[prevState.index] = resetChildRoute(prevRouteName);
|
||||
|
||||
return {
|
||||
nextState = {
|
||||
...possibleNextState,
|
||||
routes: nextRoutes,
|
||||
};
|
||||
} else {
|
||||
nextState = possibleNextState;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
return updateNextStateHistory(nextState);
|
||||
}
|
||||
|
||||
function getInitialState() {
|
||||
const routes = order.map(resetChildRoute);
|
||||
return {
|
||||
const initialState = {
|
||||
routes,
|
||||
index: initialRouteIndex,
|
||||
isTransitioning: false,
|
||||
};
|
||||
if (backBehavior === 'history') {
|
||||
const initialKey = routes[initialRouteIndex].key;
|
||||
initialState['routeKeyHistory'] = [initialKey];
|
||||
}
|
||||
return initialState;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -165,7 +186,7 @@ export default (routeConfigs, config = {}) => {
|
||||
if (activeChildState && activeChildState !== activeChildLastState) {
|
||||
const routes = [...state.routes];
|
||||
routes[state.index] = activeChildState;
|
||||
return getNextState(prevState, {
|
||||
return getNextState(action, prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
@@ -177,8 +198,21 @@ export default (routeConfigs, config = {}) => {
|
||||
const isBackEligible =
|
||||
action.key == null || action.key === activeChildLastState.key;
|
||||
if (action.type === NavigationActions.BACK) {
|
||||
if (isBackEligible && shouldBackNavigateToInitialRoute) {
|
||||
if (isBackEligible && backBehavior === 'initialRoute') {
|
||||
activeChildIndex = initialRouteIndex;
|
||||
} else if (isBackEligible && backBehavior === 'order') {
|
||||
activeChildIndex = Math.max(0, activeChildIndex - 1);
|
||||
}
|
||||
// The history contains current route, so we can only go back
|
||||
// if there is more than one item in the history
|
||||
else if (
|
||||
isBackEligible &&
|
||||
backBehavior === 'history' &&
|
||||
state.routeKeyHistory.length > 1
|
||||
) {
|
||||
const routeKey =
|
||||
state.routeKeyHistory[state.routeKeyHistory.length - 2];
|
||||
activeChildIndex = order.indexOf(routeKey);
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
@@ -226,7 +260,7 @@ export default (routeConfigs, config = {}) => {
|
||||
routes,
|
||||
index: activeChildIndex,
|
||||
};
|
||||
return getNextState(prevState, nextState);
|
||||
return getNextState(action, prevState, nextState);
|
||||
} else if (
|
||||
newChildState === childState &&
|
||||
state.index === activeChildIndex &&
|
||||
@@ -250,7 +284,7 @@ export default (routeConfigs, config = {}) => {
|
||||
...lastRoute,
|
||||
params,
|
||||
};
|
||||
return getNextState(prevState, {
|
||||
return getNextState(action, prevState, {
|
||||
...state,
|
||||
routes,
|
||||
});
|
||||
@@ -258,7 +292,7 @@ export default (routeConfigs, config = {}) => {
|
||||
}
|
||||
|
||||
if (activeChildIndex !== state.index) {
|
||||
return getNextState(prevState, {
|
||||
return getNextState(action, prevState, {
|
||||
...state,
|
||||
index: activeChildIndex,
|
||||
});
|
||||
@@ -302,7 +336,7 @@ export default (routeConfigs, config = {}) => {
|
||||
}
|
||||
|
||||
if (index !== state.index || routes !== state.routes) {
|
||||
return getNextState(prevState, {
|
||||
return getNextState(action, prevState, {
|
||||
...state,
|
||||
index,
|
||||
routes,
|
||||
|
||||
@@ -61,9 +61,12 @@ describe('SwitchRouter', () => {
|
||||
expect(state3.index).toEqual(1);
|
||||
});
|
||||
|
||||
test('handles back if given a backBehavior', () => {
|
||||
test('handles initialRoute backBehavior', () => {
|
||||
const router = getExampleRouter({ backBehavior: 'initialRoute' });
|
||||
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
expect(state.routeKeyHistory).toBeUndefined();
|
||||
|
||||
const state2 = router.getStateForAction(
|
||||
{ type: NavigationActions.NAVIGATE, routeName: 'B' },
|
||||
state
|
||||
@@ -78,6 +81,75 @@ describe('SwitchRouter', () => {
|
||||
expect(state3.index).toEqual(0);
|
||||
});
|
||||
|
||||
test('handles order backBehavior', () => {
|
||||
const routerHelper = new ExampleRouterHelper({ backBehavior: 'order' });
|
||||
expect(routerHelper.getCurrentState().routeKeyHistory).toBeUndefined();
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'C',
|
||||
})
|
||||
).toMatchObject({ index: 2 });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 1 });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 0 });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 0 });
|
||||
});
|
||||
|
||||
test('handles history backBehavior', () => {
|
||||
const routerHelper = new ExampleRouterHelper({ backBehavior: 'history' });
|
||||
expect(routerHelper.getCurrentState().routeKeyHistory).toMatchObject(['A']);
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'B',
|
||||
})
|
||||
).toMatchObject({ index: 1, routeKeyHistory: ['A', 'B'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'A',
|
||||
})
|
||||
).toMatchObject({ index: 0, routeKeyHistory: ['B', 'A'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'C',
|
||||
})
|
||||
).toMatchObject({ index: 2, routeKeyHistory: ['B', 'A', 'C'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({
|
||||
type: NavigationActions.NAVIGATE,
|
||||
routeName: 'A',
|
||||
})
|
||||
).toMatchObject({ index: 0, routeKeyHistory: ['B', 'C', 'A'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 2, routeKeyHistory: ['B', 'C'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 1, routeKeyHistory: ['B'] });
|
||||
|
||||
expect(
|
||||
routerHelper.applyAction({ type: NavigationActions.BACK })
|
||||
).toMatchObject({ index: 1, routeKeyHistory: ['B'] });
|
||||
});
|
||||
|
||||
test('handles nested actions', () => {
|
||||
const router = getExampleRouter();
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
@@ -200,10 +272,33 @@ describe('SwitchRouter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// A simple helper that makes it easier to write basic routing tests
|
||||
// As we generally want to apply one action after the other,
|
||||
// it's often convenient to manipulate a structure that keeps the router state
|
||||
class ExampleRouterHelper {
|
||||
constructor(config) {
|
||||
this._router = getExampleRouter(config);
|
||||
this._currentState = this._router.getStateForAction({
|
||||
type: NavigationActions.INIT,
|
||||
});
|
||||
}
|
||||
|
||||
applyAction = action => {
|
||||
this._currentState = this._router.getStateForAction(
|
||||
action,
|
||||
this._currentState
|
||||
);
|
||||
return this._currentState;
|
||||
};
|
||||
|
||||
getCurrentState = () => this._currentState;
|
||||
}
|
||||
|
||||
const getExampleRouter = (config = {}) => {
|
||||
const PlainScreen = () => <div />;
|
||||
const StackA = () => <div />;
|
||||
const StackB = () => <div />;
|
||||
const StackC = () => <div />;
|
||||
|
||||
StackA.router = StackRouter({
|
||||
A1: PlainScreen,
|
||||
@@ -215,6 +310,11 @@ const getExampleRouter = (config = {}) => {
|
||||
B2: PlainScreen,
|
||||
});
|
||||
|
||||
StackC.router = StackRouter({
|
||||
C1: PlainScreen,
|
||||
C2: PlainScreen,
|
||||
});
|
||||
|
||||
const router = SwitchRouter(
|
||||
{
|
||||
A: {
|
||||
@@ -225,6 +325,10 @@ const getExampleRouter = (config = {}) => {
|
||||
screen: StackB,
|
||||
path: 'great/path',
|
||||
},
|
||||
C: {
|
||||
screen: StackC,
|
||||
path: 'pathC',
|
||||
},
|
||||
},
|
||||
{
|
||||
initialRouteName: 'A',
|
||||
|
||||
Reference in New Issue
Block a user