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").
* 工具助手函数,用来比较两个路由的key
* @param one
* @param two
* @returns {number} one 比 two 大时返回 1,其他情况返回 -1
*/
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.
* 工具助手函数,比较两个场景,根据他们的索引 index 和 视图 key, 优先考虑 index,
* 1. 如果 one 的 index 比 two 的大,返回 1, 如果 one 的 index 比 two 的小,返回 -1;
* 2. 如果 one 和 two 的 index 相等, 使用它们的 key 和函数 compareKey 比较两个场景;
* @param one
* @param two
* @returns {number}
*/
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.
* 指出两个路由场景参数是否是指向同一个路由场景对象
* 两个参数会被认为指向同一个路由场景对象的情况 :
* 1. key 相等,
* 2. 并且 index 相等,
* 3. 并且属性 isStale 相等,
* 4. 并且属性 isActive 相等,
* 5. 并且通过路由浅比较函数 areRoutesShallowEqual 比较两个场景的路由的结论也是相等
* @param one
* @param two
* @returns {boolean|*|boolean}
*/
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.
* 指出两个路由参数是否指向同一个路由对象
* 两个参数会被认为指向同一个路由对象的情况 :
* 1. 两个参数对象某个是 undefined/null/0 时,直接 === 比较返回相等则被认为是相等;
* 2. 否则如果两个参数对象的 key !== 比较返回不同则认为是不相等;
* 3. 使用浅比较方法 shallowEqual 比较两个参数对象,如果返回相等则认为两个参数路由对象相等;
* @param one
* @param two
* @returns {boolean}
*/
function areRoutesShallowEqual(one, two) {
if (!one || !two) {
return one === two;
}
if (one.key !== two.key) {
return false;
}
return shallowEqual(one, two);
}
/**
* scenes reducer 函数,根据之前的状态对象 prevState 和 下一个状态对象 nextState,
* 对一组 scenes 进行归纳削减,
* @param scenes
* @param nextState
* @param prevState
* @returns {*}
* @constructor
*/
export default function ScenesReducer(scenes, nextState, prevState) {
if (prevState === nextState) {
// 如果前后状态没有变化,说明对 scenes 来讲什么都不需要做,直接返回
return scenes;
}
// 以下三个Map 的 key 都是 scene 的 key, value 是 scene 对象
const prevScenes = new Map();
const freshScenes = new Map();// 用于保存 nextState 决定的 scenes
const staleScenes = new Map();
// 对 scenes 数组中所有的 scene 对象按照它们的 isStale 属性进行分类:
// 1. isStale 为 true , 分类到 staleScenes
// 2. isStale 为 false, 分类到 prevScenes
// Populate stale scenes from previous scenes marked as stale.
scenes.forEach(scene => {
const {key} = scene;
if (scene.isStale) {
// 如果某个 scene.isStale 为真,说明这是一个过期不用的 scene,
// 将它放入 map staleScenes,
staleScenes.set(key, scene);
}
// 否则将它放入 prevScenes
prevScenes.set(key, scene);
});
const nextKeys = new Set();
nextState.routes.forEach((route, index) => {
// 构建新的场景对象的 key : route.key 加上前缀 scene_
// 注意 : 这一点说明 route 和 scene 二者的 key 是不同的,但有可派生计算关系
const key = SCENE_KEY_PREFIX + route.key;
// 构建一个新的 scene 对象
const scene = {
index,
isActive: false,
isStale: false,
key,
route,
};
invariant(
!nextKeys.has(key),
`navigation.state.routes[${index}].key "${key}" conflicts with ` +
'another route!'
);
nextKeys.add(key);
if (staleScenes.has(key)) {
// 如果某个之前被标记为 stale 的 scene 现在被 nextState 包含,说明它
// 需要被再次使用,所以需要将它从 staleScenes 中删除
// 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);
}
// 将新构建的 scene 添加到 freshScenes
freshScenes.set(key, scene);
});
if (prevState) {
// Look at the previous routes and classify any removed scenes as `stale`.
// 如果 prevState 中的某个 scene 不再被 nextState 决定的 freshScenes 包含,
// 将它们放到 staleScenes 中去
prevState.routes.forEach((route, index) => {
const key = SCENE_KEY_PREFIX + route.key;
if (freshScenes.has(key)) {
return;
}
staleScenes.set(key, {
index,
isActive: false,
isStale: true,
key,
route,
});
});
}
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.
// 如果待合并的 scene nextScene 跟之前某个 scene prevScene 是同一个,
// 则重用该 scene 对象
nextScenes.push(prevScene);
} else {
// 如果待合并的 scene nextScene 是一个新的 scene, 则直接使用该 scene 对象
nextScenes.push(nextScene);
}
};
// 合并每个 stale scene 到 nextScenes
staleScenes.forEach(mergeScene);
// 合并每个 fresh scene 到 nextScenes
// (如果 prevScenes 已经包含了待合并的某个 fresh scene ,则会重用)
freshScenes.forEach(mergeScene);
// 排序
nextScenes.sort(compareScenes);
// 统计活跃 scene 的数量,恒等式 : 应该总是为 1
let activeScenesCount = 0;
nextScenes.forEach((scene, ii) => {
// 计算某个 scene 是否处于当前活跃状态(也就是栈顶scene,用户直接看到的那个scene)
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) {
// 如果新的场景数组 nextScenes 和原来的场景数组 scenes 长度不同,
// 说明根据前后状态变化导致了场景数组的变化,所以返回新的 场景数组
// nextScenes
return nextScenes;
}
if (
nextScenes.some(
(scene, index) => !areScenesShallowEqual(scenes[index], scene)
)
) {
// 如果新的场景数组 nextScenes 和原来的场景数组 scenes 长度相同,
// 但是 scene 元素顺序发生了变化,也说明根据前后状态变化导致了场景
// 数组的变化,所以返回新的 场景数组 nextScenes
return nextScenes;
}
// 如果经过一系列计算逻辑能走到这里,说明 scenes 没有变化,直接返回它
// scenes haven't changed.
return scenes;
}
注意区分 scene 和 route 的概念,以及它们的关系,代码例子 :
const scene = {
index,
isActive: false,
isStale: false,
key,
route,
};