Move scenes reducer logic into a separate module.
Reviewed By: ericvicenti Differential Revision: D3068237 fb-gh-sync-id: 0146f38be6024c080ed4e00ac6b7c1e2f3d8e56f shipit-source-id: 0146f38be6024c080ed4e00ac6b7c1e2f3d8e56f
This commit is contained in:
parent
b7ae7d0b4e
commit
206f846507
|
@ -129,7 +129,7 @@ class NavigationCardStack extends React.Component {
|
|||
return (
|
||||
<NavigationCard
|
||||
{...props}
|
||||
key={'card_' + props.scene.navigationState.key}
|
||||
key={'card_' + props.scene.key}
|
||||
panHandlers={panHandlers}
|
||||
renderScene={this.props.renderScene}
|
||||
style={style}
|
||||
|
|
|
@ -273,6 +273,7 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
|
|||
return React.cloneElement(
|
||||
navigationBar,
|
||||
{
|
||||
key: 'header_' + props.scene.key,
|
||||
ref: this._onNavigationBarRef,
|
||||
navigator: navigationBarNavigator || this,
|
||||
navState: {...this.state},
|
||||
|
@ -319,7 +320,7 @@ class NavigationLegacyNavigator extends React.Component<any, Props, State> {
|
|||
return (
|
||||
<NavigationCard
|
||||
{...props}
|
||||
key={'card_' + props.scene.navigationState.key}
|
||||
key={'card_' + props.scene.key}
|
||||
panHandlers={panHandlers}
|
||||
renderScene={this._renderScene}
|
||||
style={style}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
const Animated = require('Animated');
|
||||
const NavigationContainer = require('NavigationContainer');
|
||||
const NavigationPropTypes = require('NavigationPropTypes');
|
||||
const NavigationStateUtils = require('NavigationStateUtils');
|
||||
const NavigationScenesReducer = require('NavigationScenesReducer');
|
||||
const React = require('react-native');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
@ -26,43 +26,8 @@ import type {
|
|||
NavigationParentState,
|
||||
NavigationScene,
|
||||
NavigationSceneRenderer,
|
||||
NavigationState,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
/**
|
||||
* Helper function to compare route keys (e.g. "9", "11").
|
||||
*/
|
||||
function compareKey(one: string, two: string): number {
|
||||
var delta = one.length - two.length;
|
||||
if (delta > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (delta < 0) {
|
||||
return -1;
|
||||
}
|
||||
return one > two ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort scenes based on their index and view key.
|
||||
*/
|
||||
function compareScenes(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene,
|
||||
): number {
|
||||
if (one.index > two.index) {
|
||||
return 1;
|
||||
}
|
||||
if (one.index < two.index) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compareKey(
|
||||
one.navigationState.key,
|
||||
two.navigationState.key,
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
applyAnimation: NavigationAnimationSetter,
|
||||
navigationState: NavigationParentState,
|
||||
|
@ -125,7 +90,7 @@ class NavigationAnimatedView
|
|||
|
||||
this.state = {
|
||||
position: new Animated.Value(this.props.navigationState.index),
|
||||
scenes: this._reduceScenes([], this.props.navigationState),
|
||||
scenes: NavigationScenesReducer([], this.props.navigationState),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -142,7 +107,7 @@ class NavigationAnimatedView
|
|||
componentWillReceiveProps(nextProps: Props): void {
|
||||
if (nextProps.navigationState !== this.props.navigationState) {
|
||||
this.setState({
|
||||
scenes: this._reduceScenes(
|
||||
scenes: NavigationScenesReducer(
|
||||
this.state.scenes,
|
||||
nextProps.navigationState,
|
||||
this.props.navigationState
|
||||
|
@ -180,37 +145,6 @@ class NavigationAnimatedView
|
|||
}
|
||||
}
|
||||
|
||||
_reduceScenes(
|
||||
scenes: Array<NavigationScene>,
|
||||
nextState: NavigationParentState,
|
||||
lastState: ?NavigationParentState
|
||||
): Array<NavigationScene> {
|
||||
const nextScenes = nextState.children.map((child, index) => {
|
||||
return {
|
||||
index,
|
||||
isStale: false,
|
||||
navigationState: child,
|
||||
};
|
||||
});
|
||||
|
||||
if (lastState) {
|
||||
lastState.children.forEach((child: NavigationState, index: number) => {
|
||||
if (
|
||||
!NavigationStateUtils.get(nextState, child.key) &&
|
||||
index !== nextState.index
|
||||
) {
|
||||
nextScenes.push({
|
||||
index,
|
||||
isStale: true,
|
||||
navigationState: child,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return nextScenes.sort(compareScenes);
|
||||
}
|
||||
|
||||
render(): ReactElement {
|
||||
const overlay = this._renderOverlay();
|
||||
const scenes = this._renderScenes();
|
||||
|
|
|
@ -45,6 +45,7 @@ export type NavigationPosition = NavigationAnimatedValue;
|
|||
export type NavigationScene = {
|
||||
index: number,
|
||||
isStale: boolean,
|
||||
key: string,
|
||||
navigationState: NavigationState,
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule NavigationScenesReducer
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {
|
||||
NavigationParentState,
|
||||
NavigationScene,
|
||||
} from 'NavigationTypeDefinition';
|
||||
|
||||
const SCENE_KEY_PREFIX = 'scene_';
|
||||
|
||||
/**
|
||||
* Helper function to compare route keys (e.g. "9", "11").
|
||||
*/
|
||||
function compareKey(one: string, two: string): number {
|
||||
var delta = one.length - two.length;
|
||||
if (delta > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (delta < 0) {
|
||||
return -1;
|
||||
}
|
||||
return one > two ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort scenes based on their index and view key.
|
||||
*/
|
||||
function compareScenes(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene,
|
||||
): number {
|
||||
if (one.index > two.index) {
|
||||
return 1;
|
||||
}
|
||||
if (one.index < two.index) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compareKey(
|
||||
one.key,
|
||||
two.key,
|
||||
);
|
||||
}
|
||||
|
||||
function areScenesShallowEqual(
|
||||
one: NavigationScene,
|
||||
two: NavigationScene,
|
||||
): boolean {
|
||||
return (
|
||||
one.key === two.key &&
|
||||
one.index === two.index &&
|
||||
one.isStale === two.isStale &&
|
||||
one.navigationState === two.navigationState &&
|
||||
one.navigationState.key === two.navigationState.key
|
||||
);
|
||||
}
|
||||
|
||||
function NavigationScenesReducer(
|
||||
scenes: Array<NavigationScene>,
|
||||
nextState: NavigationParentState,
|
||||
prevState: ?NavigationParentState,
|
||||
): Array<NavigationScene> {
|
||||
|
||||
const prevScenes = new Map();
|
||||
const freshScenes = new Map();
|
||||
const staleScenes = new Map();
|
||||
|
||||
// Populate stale scenes from previous scenes marked as stale.
|
||||
scenes.forEach(scene => {
|
||||
const {key} = scene;
|
||||
if (scene.isStale) {
|
||||
staleScenes.set(key, scene);
|
||||
}
|
||||
prevScenes.set(key, scene);
|
||||
});
|
||||
|
||||
const nextKeys = new Set();
|
||||
nextState.children.forEach((navigationState, index) => {
|
||||
const key = SCENE_KEY_PREFIX + navigationState.key;
|
||||
const scene = {
|
||||
index,
|
||||
isStale: false,
|
||||
key,
|
||||
navigationState,
|
||||
};
|
||||
invariant(
|
||||
!nextKeys.has(key),
|
||||
`navigationState.children[${index}].key "${key}" conflicts with` +
|
||||
'another child!'
|
||||
);
|
||||
nextKeys.add(key);
|
||||
|
||||
if (staleScenes.has(key)) {
|
||||
// A previously `stale` scene is now part of the nextState, so we
|
||||
// revive it by removing it from the stale scene map.
|
||||
staleScenes.delete(key);
|
||||
}
|
||||
freshScenes.set(key, scene);
|
||||
});
|
||||
|
||||
if (prevState) {
|
||||
// Look at the previous children and classify any removed scenes as `stale`.
|
||||
prevState.children.forEach((navigationState, index) => {
|
||||
const key = SCENE_KEY_PREFIX + navigationState.key;
|
||||
if (freshScenes.has(key)) {
|
||||
return;
|
||||
}
|
||||
staleScenes.set(key, {
|
||||
index,
|
||||
isStale: true,
|
||||
key,
|
||||
navigationState,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const nextScenes = [];
|
||||
|
||||
const mergeScene = (nextScene => {
|
||||
const {key} = nextScene;
|
||||
const prevScene = prevScenes.has(key) ? prevScenes.get(key) : null;
|
||||
if (prevScene && areScenesShallowEqual(prevScene, nextScene)) {
|
||||
// Reuse `prevScene` as `scene` so view can avoid unnecessary re-render.
|
||||
// This assumes that the scene's navigation state is immutable.
|
||||
nextScenes.push(prevScene);
|
||||
} else {
|
||||
nextScenes.push(nextScene);
|
||||
}
|
||||
});
|
||||
|
||||
staleScenes.forEach(mergeScene);
|
||||
freshScenes.forEach(mergeScene);
|
||||
|
||||
return nextScenes.sort(compareScenes);
|
||||
}
|
||||
|
||||
module.exports = NavigationScenesReducer;
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.dontMock('NavigationScenesReducer');
|
||||
|
||||
const NavigationScenesReducer = require('NavigationScenesReducer');
|
||||
|
||||
/**
|
||||
* Simulate scenes transtion with changes of navigation states.
|
||||
*/
|
||||
function testTransition(states) {
|
||||
const navigationStates = states.map(keys => {
|
||||
return {
|
||||
children: keys.map(key => {
|
||||
return { key };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
let scenes = [];
|
||||
let prevState = null;
|
||||
navigationStates.forEach((nextState) => {
|
||||
scenes = NavigationScenesReducer(scenes, nextState, prevState);
|
||||
prevState = nextState;
|
||||
});
|
||||
|
||||
return scenes;
|
||||
}
|
||||
|
||||
describe('NavigationScenesReducer', () => {
|
||||
|
||||
it('gets initial scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': false,
|
||||
'key': 'scene_1',
|
||||
'navigationState': {
|
||||
'key': '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 1,
|
||||
'isStale': false,
|
||||
'key': 'scene_2',
|
||||
'navigationState': {
|
||||
'key': '2'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('pushes new scenes', () => {
|
||||
// Transition from ['1', '2'] to ['1', '2', '3'].
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['1', '2', '3'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': false,
|
||||
'key': 'scene_1',
|
||||
'navigationState': {
|
||||
'key': '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 1,
|
||||
'isStale': false,
|
||||
'key': 'scene_2',
|
||||
'navigationState': {
|
||||
'key': '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 2,
|
||||
'isStale': false,
|
||||
'key': 'scene_3',
|
||||
'navigationState': {
|
||||
'key': '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('pops scenes', () => {
|
||||
// Transition from ['1', '2', '3'] to ['1', '2'].
|
||||
const scenes = testTransition([
|
||||
['1', '2', '3'],
|
||||
['1', '2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': false,
|
||||
'key': 'scene_1',
|
||||
'navigationState': {
|
||||
'key': '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 1,
|
||||
'isStale': false,
|
||||
'key': 'scene_2',
|
||||
'navigationState': {
|
||||
'key': '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 2,
|
||||
'isStale': true,
|
||||
'key': 'scene_3',
|
||||
'navigationState': {
|
||||
'key': '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('replaces scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['3'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': true,
|
||||
'key': 'scene_1',
|
||||
'navigationState': {
|
||||
'key': '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': false,
|
||||
'key': 'scene_3',
|
||||
'navigationState': {
|
||||
'key': '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 1,
|
||||
'isStale': true,
|
||||
'key': 'scene_2',
|
||||
'navigationState': {
|
||||
'key': '2'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('revives scenes', () => {
|
||||
const scenes = testTransition([
|
||||
['1', '2'],
|
||||
['3'],
|
||||
['2'],
|
||||
]);
|
||||
|
||||
expect(scenes).toEqual([
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': true,
|
||||
'key': 'scene_1',
|
||||
'navigationState': {
|
||||
'key': '1'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': false,
|
||||
'key': 'scene_2',
|
||||
'navigationState': {
|
||||
'key': '2'
|
||||
},
|
||||
},
|
||||
{
|
||||
'index': 0,
|
||||
'isStale': true,
|
||||
'key': 'scene_3',
|
||||
'navigationState': {
|
||||
'key': '3'
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue