Files
react-navigation/src/views/ScenesReducer.js
Eric Vicenti e27ad22c57 [BREAKING] New createNavigator API (#3392)
* New createNavigator and View API

See the RFC here:
https://github.com/react-navigation/rfcs/blob/master/text/0002-navigator-view-api.md

* shattered dreams of flow

* fix export

* Fix tab view issues found by brent
2018-02-27 17:27:58 -08:00

189 lines
4.1 KiB
JavaScript

import invariant from '../utils/invariant';
import shallowEqual from '../utils/shallowEqual';
const SCENE_KEY_PREFIX = 'scene_';
/**
* Helper function to compare route keys (e.g. "9", "11").
*/
function compareKey(one, two) {
const 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, two) {
if (one.index > two.index) {
return 1;
}
if (one.index < two.index) {
return -1;
}
return compareKey(one.key, two.key);
}
/**
* Whether two routes are the same.
*/
function areScenesShallowEqual(one, two) {
return (
one.key === two.key &&
one.index === two.index &&
one.isStale === two.isStale &&
one.isActive === two.isActive &&
areRoutesShallowEqual(one.route, two.route)
);
}
/**
* Whether two routes are the same.
*/
function areRoutesShallowEqual(one, two) {
if (!one || !two) {
return one === two;
}
if (one.key !== two.key) {
return false;
}
return shallowEqual(one, two);
}
export default function ScenesReducer(
scenes,
nextState,
prevState,
descriptors
) {
if (prevState === nextState) {
return scenes;
}
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.routes.forEach((route, index) => {
const key = SCENE_KEY_PREFIX + route.key;
let descriptor = descriptors && descriptors[route.key];
const scene = {
index,
isActive: false,
isStale: false,
key,
route,
descriptor,
};
invariant(
!nextKeys.has(key),
`navigation.state.routes[${index}].key "${key}" conflicts with ` +
'another route!'
);
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 routes and classify any removed scenes as `stale`.
prevState.routes.forEach((route, index) => {
const key = SCENE_KEY_PREFIX + route.key;
if (freshScenes.has(key)) {
return;
}
const lastScene = scenes.find(scene => scene.route.key === route.key);
const descriptor = lastScene && lastScene.descriptor;
staleScenes.set(key, {
index,
isActive: false,
isStale: true,
key,
route,
descriptor,
});
});
}
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);
nextScenes.sort(compareScenes);
let activeScenesCount = 0;
nextScenes.forEach((scene, ii) => {
const isActive = !scene.isStale && scene.index === nextState.index;
if (isActive !== scene.isActive) {
nextScenes[ii] = {
...scene,
isActive,
};
}
if (isActive) {
activeScenesCount++;
}
});
invariant(
activeScenesCount === 1,
'there should always be only one scene active, not %s.',
activeScenesCount
);
if (nextScenes.length !== scenes.length) {
return nextScenes;
}
if (
nextScenes.some(
(scene, index) => !areScenesShallowEqual(scenes[index], scene)
)
) {
return nextScenes;
}
// scenes haven't changed.
return scenes;
}