2016-03-21 18:13:59 +00:00
|
|
|
/**
|
|
|
|
* 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 {
|
2016-05-21 01:09:57 +00:00
|
|
|
NavigationRoute,
|
2016-03-21 18:13:59 +00:00
|
|
|
NavigationScene,
|
2016-05-21 01:09:57 +00:00
|
|
|
NavigationState,
|
2016-03-21 18:13:59 +00:00
|
|
|
} from 'NavigationTypeDefinition';
|
|
|
|
|
|
|
|
const SCENE_KEY_PREFIX = 'scene_';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to compare route keys (e.g. "9", "11").
|
|
|
|
*/
|
|
|
|
function compareKey(one: string, two: string): number {
|
2016-05-18 20:32:52 +00:00
|
|
|
const delta = one.length - two.length;
|
2016-03-21 18:13:59 +00:00
|
|
|
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 &&
|
2016-05-21 01:09:57 +00:00
|
|
|
one.route === two.route &&
|
|
|
|
one.route.key === two.route.key
|
2016-03-21 18:13:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function NavigationScenesReducer(
|
|
|
|
scenes: Array<NavigationScene>,
|
2016-05-20 21:24:24 +00:00
|
|
|
nextState: NavigationState,
|
|
|
|
prevState: ?NavigationState,
|
2016-03-21 18:13:59 +00:00
|
|
|
): Array<NavigationScene> {
|
2016-05-18 20:32:52 +00:00
|
|
|
if (prevState === nextState) {
|
|
|
|
return scenes;
|
|
|
|
}
|
2016-03-21 18:13:59 +00:00
|
|
|
|
2016-05-21 01:09:57 +00:00
|
|
|
const prevScenes: Map<string, NavigationScene> = new Map();
|
|
|
|
const freshScenes: Map<string, NavigationScene> = new Map();
|
|
|
|
const staleScenes: Map<string, NavigationScene> = new Map();
|
2016-03-21 18:13:59 +00:00
|
|
|
|
|
|
|
// 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();
|
2016-05-22 23:27:53 +00:00
|
|
|
nextState.routes.forEach((route, index) => {
|
2016-05-21 01:09:57 +00:00
|
|
|
const key = SCENE_KEY_PREFIX + route.key;
|
2016-03-21 18:13:59 +00:00
|
|
|
const scene = {
|
|
|
|
index,
|
|
|
|
isStale: false,
|
|
|
|
key,
|
2016-05-21 01:09:57 +00:00
|
|
|
route,
|
2016-03-21 18:13:59 +00:00
|
|
|
};
|
|
|
|
invariant(
|
|
|
|
!nextKeys.has(key),
|
2016-05-22 23:27:53 +00:00
|
|
|
`navigationState.routes[${index}].key "${key}" conflicts with` +
|
|
|
|
'another route!'
|
2016-03-21 18:13:59 +00:00
|
|
|
);
|
|
|
|
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) {
|
2016-05-22 23:27:53 +00:00
|
|
|
// Look at the previous routes and classify any removed scenes as `stale`.
|
|
|
|
prevState.routes.forEach((route: NavigationRoute, index) => {
|
2016-05-21 01:09:57 +00:00
|
|
|
const key = SCENE_KEY_PREFIX + route.key;
|
2016-03-21 18:13:59 +00:00
|
|
|
if (freshScenes.has(key)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
staleScenes.set(key, {
|
|
|
|
index,
|
|
|
|
isStale: true,
|
|
|
|
key,
|
2016-05-21 01:09:57 +00:00
|
|
|
route,
|
2016-03-21 18:13:59 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|