import React from 'react';
import {Linking} from 'react-native';
import {BackHandler} from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant';
/**
* Create an HOC that injects the navigation and manages the navigation state
* in case it's not passed from above.
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
* components.
*
* 高阶组件,封装一个组件(实际上是一个navigator导航器组件)并向其注入导航属性(navigation),
* 并在导航状态没有从上面传入时管理导航状态,这样 StackNavigator和 TabNavigator 就可以被
* 作为顶层组件来使用
*
* @param Component 由 createNavigator 创建的 navigator 组件,它有一个静态属性 router,表示路由器
* @returns {NavigationContainer}
*/
export default function createNavigationContainer(Component) {
class NavigationContainer extends React.Component {
subs = null;
// 获取 导航器组件的静态属性 router
static router = Component.router;
static navigationOptions = null;
// action 事件监听器,事件订阅者 的集合
_actionEventSubscribers = new Set();
constructor(props) {
super(props);
// 验证当前组件的属性 this.props 的格式是否正确 :
// 1. 如果这是一个有状态组件,不验证直接返回;
// 2. 如果这是一个无状态组件, 那它的属性 this.props 中只能最多包含两个属性 :
// navigation,screenProps,否则抛出Error
this._validateProps(props);
this._initialAction = NavigationActions.init();
if (this._isStateful()) {
// 增加对硬件事件 hardwareBackPress (返回按钮被按下) 的事件监听逻辑并记录该订阅
this.subs = BackHandler.addEventListener('hardwareBackPress', () => {
if (!this._isMounted) {
this.subs && this.subs.remove();
} else {
// dispatch returns true if the action results in a state change,
// and false otherwise. This maps well to what BackHandler expects
// from a callback -- true if handled, false if not handled
return this.dispatch(NavigationActions.back());
}
});
}
// 当前组件状态的初始化: 如果时有状态组件,nav 初始化为路由器的初始状态, 否则设置为 null
this.state = {
nav: this._isStateful()
? Component.router.getStateForAction(this._initialAction)
: null,
};
}
/**
* 检测当前组件是否是状态组件
* @returns {boolean}
* @private
*/
_isStateful() {
// 如果当前组件实例的 this.props.navigation 属性没有提供,则认为当前组件是一个有状态组件,
return !this.props.navigation;
}
/**
* 验证当前组件的属性 this.props 的格式是否正确 :
* 1. 如果这是一个有状态组件,不验证直接返回;
* 2. 如果这是一个无状态组件, 那它的属性 this.props 中只能最多包含两个属性 :
* navigation,screenProps, 否则抛出Error
* @param props
* @private
*/
_validateProps(props) {
if (this._isStateful()) {
// 如果这是一个有状态组件(this.props.navigation),那么不需要验证 this.props
return;
}
const {navigation, screenProps, ...containerProps} = props;
const keys = Object.keys(containerProps);
// 这里其实隐含地说明了一个限制:
// 1. 当前组件要么是有状态的,通过组件实例属性(除this.props之外的其它实例属性)来管理状态,
// 2. 要么是无状态的,通过 this.props.navigation 属性来管理状态,
// 这两种情况是互斥的,下面的if语句部分说明了这一点,当 this.props 中包含了除
// {navigation,screenProps}之外其他任何属性的时候,就认为不知道是上面两种情况中的哪一种了,
// 这里的措施是直接抛出一个 Error
if (keys.length !== 0) {
throw new Error(
'This navigator has both navigation and container props, so it is ' +
`unclear if it should own its own state. Remove props: "${keys.join(
', '
)}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.'
);
}
}
/**
* 工具函数,从名字上看,将一个 url 分解成路径 path 部分和参数 params 部分,
* 1.实际上该函数并没有分析 url 中的参数,所以返回的参数部分为 {}
* 2.所分析得到的路径 path 部分指的是 url 中协议分隔符 :// 后面的部分
* 举例:
* 参数 url 是 : "https://www.google.com?q=123"
* 返回结果是 : { path: 'www.google.com?q=123', params: {} }
* @param url
* @returns {{path: (*|string), params}}
* @private
*/
_urlToPathAndParams(url) {
const params = {};
const delimiter = this.props.uriPrefix || '://';
let path = url.split(delimiter)[1];
if (typeof path === 'undefined') {
path = url;
} else if (path === '') {
path = '/';
}
return {
path,
params,
};
}
/**
* 处理打开一个 url
* @param url
* @private
*/
_handleOpenURL = ({url}) => {
const parsedUrl = this._urlToPathAndParams(url);
if (parsedUrl) {
const {path, params} = parsedUrl;
const action = Component.router.getActionForPathAndParams(path, params);
if (action) {
this.dispatch(action);
}
}
};
/**
* 某个指令 action 导致了导航状态发生变化时此方法会被调用,并且如果外部指定了
* this.props.onNavigationStateChange 的话,this.props.onNavigationStateChange
* 会被调用,参数列表和本函数一样
* @param prevNav action 执行之前的导航状态
* @param nav action 执行之后的导航状态
* @param action 导致状态发生变化的 acction
* @private
*/
_onNavigationStateChange(prevNav, nav, action) {
if (
typeof this.props.onNavigationStateChange === 'undefined' &&
this._isStateful() &&
!!process.env.REACT_NAV_LOGGING
) {
/* eslint-disable no-console */
if (console.group) {
console.group('Navigation Dispatch: ');
console.log('Action: ', action);
console.log('New State: ', nav);
console.log('Last State: ', prevNav);
console.groupEnd();
} else {
console.log('Navigation Dispatch: ', {
action,
newState: nav,
lastState: prevNav,
});
}
/* eslint-enable no-console */
return;
}
// 如果外部指定了 this.props.onNavigationStateChange ,
// 调用 this.props.onNavigationStateChange
if (typeof this.props.onNavigationStateChange === 'function') {
this.props.onNavigationStateChange(prevNav, nav, action);
}
}
componentWillReceiveProps(nextProps) {
this._validateProps(nextProps);
}
componentDidUpdate() {
// Clear cached _nav every tick
if (this._nav === this.state.nav) {
this._nav = null;
}
}
componentDidMount() {
this._isMounted = true;
if (!this._isStateful()) {
return;
}
Linking.addEventListener('url', this._handleOpenURL);
Linking.getInitialURL().then(url => url && this._handleOpenURL({url}));
/**
* 向每个 action 事件订阅者通知 初始化 action
*/
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action: this._initialAction,
state: this.state.nav,
lastState: null,
})
);
}
componentWillUnmount() {
this._isMounted = false;
Linking.removeEventListener('url', this._handleOpenURL);
this.subs && this.subs.remove();
}
// Per-tick temporary storage for state.nav
/**
* 派发 action
* @param action
* @returns {boolean}
*/
dispatch = action => {
if (!this._isStateful()) {
// 如果当前组件是无状态的,则不做事件派发
return false;
}
this._nav = this._nav || this.state.nav;// 获取当前导航状态到 this._nav
const oldNav = this._nav; // 将当前导航状态记录到 oldNav,
// 因为接下来要根据这个状态和要派发的 action 来计算 action 后可能到达的状态,
// 所以这里记录为 oldNav 可以和目标状态有个明确的区分
invariant(oldNav, 'should be set in constructor if stateful');
// 让路由器基于当前导航状态 oldNav 和待派发事件 action 计算出目标导航状态是什么,记录到 nav
const nav = Component.router.getStateForAction(action, oldNav);
// 准备一个函数,用于向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
state: nav,
lastState: oldNav,
})
);
};
if (nav && nav !== oldNav) {
// 如果当前导航状态 oldNav 和待派发事件 action计算出的目标 nav 和当前的 oldNav 不同,
// 则要通过this.setState()设置最新导航状态 nav,并在设置动作完成时发布相关事件和进行
// 回调调用,这里需要注意的一点是 this.setState()是一步的,它的第二个参数是异步任务完
// 成时的回调逻辑
// Cache updates to state.nav during the tick to ensure that subsequent calls will
// not discard this change
this._nav = nav;
this.setState({nav}, () => {
this._onNavigationStateChange(oldNav, nav, action);
// 向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
dispatchActionEvents();
});
return true;
} else {
// 向 action 事件订阅器通知有一个值为 ${action} 的 action 事件
dispatchActionEvents();
}
return false;
};
render() {
let navigation = this.props.navigation;
if (this._isStateful()) {
const nav = this.state.nav;
invariant(nav, 'should be set in constructor if stateful');
if (!this._navigation || this._navigation.state !== nav) {
// 如果导航状态发生了变化,重新构建一个 navigation 对象并做相应的增强,
// 增强逻辑参考 addNavigationHelpers 的函数的实现
this._navigation = addNavigationHelpers({
dispatch: this.dispatch, // 管理 action 派发函数
state: nav, // 关联新的导航状态
addListener: (eventName, handler) => {
if (eventName !== 'action') {
return {
remove: () => {
}
};
}
this._actionEventSubscribers.add(handler);
return {
remove: () => {
this._actionEventSubscribers.delete(handler);
},
};
},
});
}
navigation = this._navigation;
}
invariant(navigation, 'failed to get navigation');
return <Component {...this.props} navigation={navigation}/>;
}
}
return NavigationContainer;
}
React Navigation源代码阅读 : createNavigationContainer.js
最新推荐文章于 2023-08-26 15:40:26 发布