mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-11 22:33:32 +08:00
* 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
189 lines
4.1 KiB
JavaScript
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;
|
|
}
|