import invariant from '../utils/invariant';
import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions';
import validateRouteConfigMap from './validateRouteConfigMap';
import getScreenConfigDeprecated from './getScreenConfigDeprecated';
function childrenUpdateWithoutSwitchingIndex(actionType) {
return [
NavigationActions.SET_PARAMS,
NavigationActions.COMPLETE_TRANSITION,
].includes(actionType);
}
/**
* 定义一个选项卡路由器
* @param routeConfigs 路由配置信息
* @param config 路由器/导航器设置
* @returns {*}
*/
export default (routeConfigs, config = {}) => {
// Fail fast on invalid route definitions
// 路由配置信息格式检查,如果格式不合格,直接抛出错误不再继续
validateRouteConfigMap(routeConfigs);
// 确定路由配置中各个路由的顺序, order 是一个以路由的路由名称为元素的集合,
// 要么通过 config.order 来指定,要么使用路由配置中缺省的定义顺序
const order = config.order || Object.keys(routeConfigs);
//确定各个路由的 path,所有路由的 path 信息由 config.paths 指定
const paths = config.paths || {};
// 获取缺省路由参数
const initialRouteParams = config.initialRouteParams;
// 获取缺省路由名称 : 或者是 config.initialRouteName 明确指定,或者使用
// 路由顺序定义集合中排在首位的那个路由作为缺省路由名称
const initialRouteName = config.initialRouteName || order[0];
// 获取缺省路由的索引
const initialRouteIndex = order.indexOf(initialRouteName);
// 确定返回行为,要么使用 config.backBehavior 指定的返回目标,要么使用
// 缺省的返回目标 `initialRoute` (表示返回 initialRouteName 定义的缺省路由)
const backBehavior = config.backBehavior || 'initialRoute';
const shouldBackNavigateToInitialRoute = backBehavior === 'initialRoute';
// 获取每个子路由的路由器信息:
// 1.如果某个子路由是一个嵌套路由器,则对应的 tabRouters 元素设置为相应的 路由器信息
// 2.如果某个子路由是一个普通组件,则对应的 tabRouters 元素设置为 null
const tabRouters = {};
order.forEach(routeName => {
const routeConfig = routeConfigs[routeName];
paths[routeName] =
typeof routeConfig.path === 'string' ? routeConfig.path : routeName;
tabRouters[routeName] = null;
const screen = getScreenForRouteName(routeConfigs, routeName);
if (screen.router) {
tabRouters[routeName] = screen.router;
}
});
if (initialRouteIndex === -1) {
// 遇到了无效的初始路由设置,抛出错误
throw new Error(
`Invalid initialRouteName '${initialRouteName}' for TabRouter. ` +
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
);
}
// 定义和返回 TabRouter 对象
return {
/**
* 计算在某个导航状态 inputState 上应用指令 action 得到的目标导航状态
* @param action 将要执行的 action
* @param inputState action 执行前的状态,action 将要应用到的导航状态
* @returns {*}
*/
getStateForAction(action, inputState) {
// Establish a default state
let state = inputState;
if (!state) {
// 如果没有通过 inputState 指定导航状态,则这里构造一个初始导航状态
// 对每个选项卡(Tab)对应的子路由初初始化其路由状态,每个选项卡的状态
// 放在一起构成了整个选项卡路由器的初始导航状态,类似这样子:
// [{选项卡0子路由的导航状态},{选项卡1子路由的导航状态},...,选项卡n子路由的导航状态]
const routes = order.map(routeName => {
// 获取当前子路由的参数
const params =
routeName === initialRouteName ? initialRouteParams : undefined;
// 获取当前子路由的路由器信息
const tabRouter = tabRouters[routeName];
if (tabRouter) {
// 当前子路由是一个嵌套路由器,行初始化它的状态并返回
const childAction = NavigationActions.init();
return {
...tabRouter.getStateForAction(childAction),
key: routeName, // 注意:这里 key 使用了路由名称
routeName, // 注意:这里 routeName 还是使用了路由名称
params,
};
}
// 当前子路由是一个普通屏幕组件,也对它的导航状态做初始化并返回
return {
key: routeName, // 注意:这里 key 使用了路由名称
routeName, // 注意:这里 routeName 还是使用了路由名称
params,
};
});
// 基于各个子路由的导航状态构建整个选项卡路由器的导航状态
state = {
routes,
index: initialRouteIndex,
isTransitioning: false,
};
// console.log(`${order.join('-')}: Initial state`, {state});
}
// 上面根据参数 inputState 决定应用 action 的起始状态对象 state,以上逻辑上可以总结为 :
// 1. 如果指定了 inputState,使用它作为将要应用 action 的导航状态对象 state
// 2. 如果没有指定 inputState,使用初始导航状态对象作为将要应用 action 的导航状态对象 state
if (action.type === NavigationActions.INIT) {
// 这是一个对选项卡路由器进行初始化的 action, 此时 inputState 应该未指定,
// 而 state 现在是一个初始状态, 现在如果 action.params 有信息的话, 把它
// 合并到每个子路由的路由参数 params 中去
// Merge any params from the action into all the child routes
const {params} = action;
if (params) {
state.routes = state.routes.map(route => ({
...route,
params: {
...route.params,
...params,
...(route.routeName === initialRouteName
? initialRouteParams
: null),
},
}));
}
}
// Let the current tab handle it
// 先尝试看看当前选项卡子路由能否处理此 action , 如果当前选项卡子路由能否处理此 action (当前选项卡子路由
// 是一个嵌套子路由器并且负责该 action 的处理),此函数的任务在下面的if语句块中就完成返回了。
const activeTabLastState = state.routes[state.index];
const activeTabRouter = tabRouters[order[state.index]];
if (activeTabRouter) {
const activeTabState = activeTabRouter.getStateForAction(
action,
activeTabLastState
);
if (!activeTabState && inputState) {
// 当前选项卡子路由能处理此 action 并且处理前后状态无变化
return null;
}
if (activeTabState && activeTabState !== activeTabLastState) {
// 当前选项卡子路由能处理此 action 并且处理前后状态有变化
const routes = [...state.routes];
routes[state.index] = activeTabState;
return {
...state,
routes,
};
}
}
// 下面是要做Tab切换了。Tab切换逻辑放在上面的当前子路由处理action尝试之后,目的是为了让内部
// Tab子路由先有机会发生变化。
// Handle tab changing. Do this after letting the current tab try to
// handle the action, to allow inner tabs to change first
let activeTabIndex = state.index;
// 先对是否要执行 BACK action 进行处理
// 是否符合执行BACK action的条件
const isBackEligible =
action.key == null || action.key === activeTabLastState.key;
if (action.type === NavigationActions.BACK) {
// action 是一个 BACK action
if (isBackEligible && shouldBackNavigateToInitialRoute) {
// action 是一个 BACK action 并且需要返回到初始路由
activeTabIndex = initialRouteIndex;
} else {
// 如果 action 是一个 BACK action 但是不符合返回执行条件或者
// 不需要返回初始路由,那就什么都不做,返回原导航状态对象
return state;
}
}
// 对 NAVIGATE action 的处理
let didNavigate = false;// 用于记录导航动作是否真的会发生
if (action.type === NavigationActions.NAVIGATE) {
const navigateAction = action;
didNavigate = !!order.find((tabId, i) => {
if (tabId === navigateAction.routeName) {
activeTabIndex = i;
return true;
}
return false;
});
if (didNavigate) {
const childState = state.routes[activeTabIndex];
let newChildState;
const tabRouter = tabRouters[action.routeName];
if (action.action) {
newChildState = tabRouter
? tabRouter.getStateForAction(action.action, childState)
: null;
} else if (!tabRouter && action.params) {
newChildState = {
...childState,
params: {
...(childState.params || {}),
...action.params,
},
};
}
if (newChildState && newChildState !== childState) {
const routes = [...state.routes];
routes[activeTabIndex] = newChildState;
return {
...state,
routes,
index: activeTabIndex,
};
}
}
}
// 对 SET_PARAMS action 的处理
if (action.type === NavigationActions.SET_PARAMS) {
const key = action.key;
const lastRoute = state.routes.find(route => route.key === key);
if (lastRoute) {
const params = {
...lastRoute.params,
...action.params,
};
const routes = [...state.routes];
routes[state.routes.indexOf(lastRoute)] = {
...lastRoute,
params,
};
return {
...state,
routes,
};
}
}
if (activeTabIndex !== state.index) {
return {
...state,
index: activeTabIndex,
};
} else if (didNavigate && !inputState) {
return state;
} else if (didNavigate) {
return null;
}
// Let other tabs handle it and switch to the first tab that returns a new state
let index = state.index;
let routes = state.routes;
order.find((tabId, i) => {
const tabRouter = tabRouters[tabId];
if (i === index) {
return false;
}
let tabState = routes[i];
if (tabRouter) {
// console.log(`${order.join('-')}: Processing child router:`, {action, tabState});
tabState = tabRouter.getStateForAction(action, tabState);
}
if (!tabState) {
index = i;
return true;
}
if (tabState !== routes[i]) {
routes = [...routes];
routes[i] = tabState;
index = i;
return true;
}
return false;
});
// console.log(`${order.join('-')}: Processed other tabs:`, {lastIndex: state.index, index});
// Nested routers can be updated after switching tabs with actions such as SET_PARAMS
// and COMPLETE_TRANSITION.
// NOTE: This may be problematic with custom routers because we whitelist the actions
// that can be handled by child routers without automatically changing index.
if (childrenUpdateWithoutSwitchingIndex(action.type)) {
index = state.index;
}
if (index !== state.index || routes !== state.routes) {
return {
...state,
index,
routes,
};
}
return state;
},
// 获取当前活跃选项卡子路由屏幕组件,或者如果当前活跃选项卡子路由是一个嵌套路由器,递归查找
// 其当前活跃屏幕组件
getComponentForState(state) {
const routeName = state.routes[state.index].routeName;
invariant(
routeName,
`There is no route defined for index ${state.index}. Check that
that you passed in a navigation state with a valid tab/screen index.`
);
const childRouter = tabRouters[routeName];
if (childRouter) {
return childRouter.getComponentForState(state.routes[state.index]);
}
return getScreenForRouteName(routeConfigs, routeName);
},
/**
* 获取选项卡子路由的 screen 属性的值(如果子路由是嵌套路由器,不递归查找)
* @param routeName
* @returns {*}
*/
getComponentForRouteName(routeName) {
return getScreenForRouteName(routeConfigs, routeName);
},
/**
* 获取当前导航状态对象的路径和参数信息
* @param state
* @returns {{path: *, params: *}}
*/
getPathAndParamsForState(state) {
const route = state.routes[state.index];
const routeName = order[state.index];
const subPath = paths[routeName];
const screen = getScreenForRouteName(routeConfigs, routeName);
let path = subPath;
let params = route.params;
if (screen && screen.router) {
const stateRoute = route;
// If it has a router it's a navigator.
// If it doesn't have router it's an ordinary React component.
const child = screen.router.getPathAndParamsForState(stateRoute);
path = subPath ? `${subPath}/${child.path}` : child.path;
params = child.params ? {...params, ...child.params} : params;
}
return {
path,
params,
};
},
/**
* Gets an optional action, based on a relative path and query params.
*
* This will return null if there is no action matched
*/
getActionForPathAndParams(path, params) {
return (
order
.map(tabId => {
const parts = path.split('/');
const pathToTest = paths[tabId];
if (parts[0] === pathToTest) {
const tabRouter = tabRouters[tabId];
const action = NavigationActions.navigate({
routeName: tabId,
});
if (tabRouter && tabRouter.getActionForPathAndParams) {
action.action = tabRouter.getActionForPathAndParams(
parts.slice(1).join('/'),
params
);
} else if (params) {
action.params = params;
}
return action;
}
return null;
})
.find(action => !!action) ||
order
.map(tabId => {
const tabRouter = tabRouters[tabId];
return (
tabRouter && tabRouter.getActionForPathAndParams(path, params)
);
})
.find(action => !!action) ||
null
);
},
getScreenOptions: createConfigGetter(
routeConfigs,
config.navigationOptions
),
getScreenConfig: getScreenConfigDeprecated,
};
};
React Navigation源代码阅读 :routers/TabRouter.js
猜你喜欢
转载自blog.csdn.net/andy_zhang2007/article/details/80316916
今日推荐
周排行