react-navigation的基礎認識:
react-navigation是官方正在推的導航,但是沒有放入到react-native包中,而是單獨開了一個庫,它的核心概念是Router,Router的概念圖如下所示:
最上方的文字:
上面這個圖清晰的表達出了Router能幹什麼,最重要的一句話是:Router經常組合使用,並且能代理子路由,這句話的意思我待會分析原始碼來深入瞭解。
上圖的右部分:
由Action而引起State的變化,是不是很像Redux?後面我會寫篇文章專門寫如何配合Redux自定義行為。在不配合Redux使用時,它自己內部其實也通過createNavigationContainer(後邊原始碼分析會說到)來作為主容器維護這類似Redux形式的資料流。使用者在使用App的過程中觸發導航Action,例如StackRouter的Navigate,goBack等,這些Action被Dispatch出去後會被router.getStateForAction(action, lastState)處理,getStateForAction通過Action和之前的State獲得新的State。這就是一個完整的資料流過程。
上圖的左部分:
除了在App執行過程中使用者主動觸發goBack,navigate這些Action外,當App不在執行時,也可以通過推送,通知等喚醒App來派發Action,router.getActionForPathAndParams通過path和params可以取得Action,然後就可以走圖右部分的流程了。
由上面的分析可以知道,router是用來管理導航狀態,它的原理就是通過派發Action來獲得新的State,從而維護和管理導航的狀態,導航的State的結構如下:
//MainTabState:
{
index: 0,
routes: [
{
key: 'key-1',
routeName: 'Home',
...
},
{
key: 'key-2',
routeName: 'SomeTab',
index: 2,
routes:[...]
},
{
key: 'key-3',
routeName: 'Mine',
...
}
]
}複製程式碼
MainTab有3個Tab,分別是Home,SomeTab, Mine,當前MainTab停留在Home上,SomeTab也是一個TabNavigator,它停留在第3個Tab上(index從0開始)。index表示導航的當前位置,routes表示導航中已經存在的screen,每個screen都有唯一的key標識,screen也可以是Navigator,比如SomeTab。
上面對Router有了大概的瞭解,注意是Router,因為後面會說到route,經常會混淆。Router是用來管理導航的State,是導航的Core,StackNavigator的navigate,back操作的邏輯都是由StackRouter處理的,TabNavigator的navigate,back操作的邏輯都是由TabRouter處理的。用Router的State來渲染當前Screen,管理手勢和動畫的任務,就要配合Navigation View和Transitioner了。
Navigation View
Transitioner
說在前邊的話
這篇文章是分析導航路由的實現原理,而不是講解react-navigation的基礎用法,需要了解react-navigation的基礎用法,請移步官網:reactnavigation.org/。
深入淺出Router
什麼是Router?Router是Navigator的核心,有了Router就可以自定義Navigator了:
class MyNavigator extends React.Component {
static router = StackRouter(routes, config);
...
}複製程式碼
react-navigation有兩個內建的Router,分別是StackRouter和TabRouter,它們都提供了一套基礎的Router API:
- getStateForAction(action, state)
- getComponentForRouteName(routeName)
- getComponentForState(state)
- getActionForPathAndParams(path, params)
- getPathAndParamsForState(state)
- getScreenOptions(navigation, screenProps)
StackNavigator:
用法:
const ModalStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},
Profile: {
screen: MyProfileScreen,
},
});複製程式碼
StackNavigator
的程式碼實現:
github.com/react-commu…
//摘錄StackNavigator.js
export default (
routeConfigMap: NavigationRouteConfigMap,
stackConfig: StackNavigatorConfig = {}
) => {
...
const router = StackRouter(routeConfigMap, stackRouterConfig);
const navigator = createNavigator(
router,
routeConfigMap,
stackConfig,
NavigatorTypes.STACK
)((props: *) => (
<CardStackTransitioner
{...props}
...
/>
));
return createNavigationContainer(navigator, stackConfig.containerOptions);
};複製程式碼
使用StackRouter工廠方法生成router
,傳入createNavigator
。
createNavigator
的實現:
github.com/react-commu…
const createNavigator = (
router: NavigationRouter<*, *, *>,
routeConfigs: NavigationRouteConfigMap,
navigatorConfig: any,
navigatorType: NavigatorType
) => (View: NavigationNavigator<*, *, *, *>) => {
class Navigator extends React.Component {
props: NavigationNavigatorProps<*>;
static router = router;
...
render() {
return <View {...this.props} router={router} />;
}
}
return Navigator;
};複製程式碼
createNavigator會返回一個函式,這個函式需要傳入View,這個View就是
(props: *) => (
<CardStackTransitioner
{...props}
...
/>
)複製程式碼
router作為屬性
傳入到VIew中,用來渲染介面,關於使用router渲染screen的分析下篇再說。CardStackTransitioner管理了CardStack的轉場動畫,手勢返回,createNavigator函式返回的函式傳入VIew引數後還是返回一個Navigator(擁有router靜態屬性就把它當做Navigator)。
createNavigationContainer
:的實現
github.com/react-commu…
export default function createNavigationContainer<T: *>(
Component: ReactClass<NavigationNavigatorProps<T>>,
containerOptions?: {}
) {
...
class NavigationContainer extends React.Component<void, Props<T>, State> {
state: State;
props: Props<T>;
//NavigationConatainer也是一個路由
static router = Component.router;
constructor(props: Props<T>) {
super(props);
...
this.state = {
nav: this._isStateful()
? Component.router.getStateForAction(NavigationActions.init())
: null,
};
}
//當外部元件沒有傳入navigation屬性時,自己處理狀態
_isStateful(): boolean {
return !this.props.navigation;
}
...
//與Redex的dispatch同名,方便接入Redux,用意也相同,派發Action,通過getStateForAction改變State,從而重新整理元件
dispatch = (action: NavigationAction) => {
const { state } = this;
if (!this._isStateful()) {
return false;
}
const nav = Component.router.getStateForAction(action, state.nav);
if (nav && nav !== state.nav) {
this.setState({ nav }, () =>
...
);
...
}
...
};
//關於android back的處理
....
_navigation: ?NavigationScreenProp<NavigationRoute, NavigationAction>;
render() {
let navigation = this.props.navigation;
//只有外部元件沒有傳入navigation時才自己建立navigation
if (this._isStateful()) {
//不存在navigation或者state.nav發生變化,重新獲取navigation
if (!this._navigation || this._navigation.state !== this.state.nav) {
this._navigation = addNavigationHelpers({
dispatch: this.dispatch,
state: this.state.nav,
});
}
navigation = this._navigation;
}
//將navigtion作為屬性傳給元件,這就是Container名稱的來意
return <Component {...this.props} navigation={navigation} />;
}
}
return NavigationContainer;
}複製程式碼
所以如果外部傳入了navigation屬性,NavigationContainer就不做任何事情,就要直接渲染出Component並把屬性往下傳遞,如果沒有navigation屬性,則自己充當container,派發Action,管理State,重新整理Navigator。
總結:
由routeConfig建立router,由router建立navigator,然後由navigator建立了createNavigationContainer,在NavigationContainer中使用isStateful判斷是否作為container使用(自己充當container,派發Action,管理State,重新整理Navigator)。
StackRouter:
StackRouter
的程式碼實現:
github.com/react-commu…
由上面的程式碼分析可以看出,通過StackRouter(routeConfigMap, stackRouterConfig)
建立的router最終作為了NavigationContainer的靜態屬性。那麼StackRouter(routeConfigMap, stackRouterConfig)
建立的router是什麼樣的呢?
第一步:解析路由配置
const ModalStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},
Profile: {
screen: MyProfileScreen,
},
});複製程式碼
呼叫StackNavigator
工廠方法的第一個引數就是routeConfigs
const childRouters = {};
const routeNames = Object.keys(routeConfigs);
console.log('開始解析路由配置...');
routeNames.forEach((routeName: string) => {
const screen = getScreenForRouteName(routeConfigs, routeName);
//前面說過,通過router來判斷是否為Navigator
if (screen && screen.router) {
// If it has a router it's a navigator.
//這對後面路由的巢狀處理特別關鍵
childRouters[routeName] = screen.router;
} else {
// If it doesn't have router it's an ordinary React component.
childRouters[routeName] = null;
}
console.log('路由配置解析結果:');
console.log(JSON.stringify(childRouters))
});複製程式碼
由路由配置生成相應的childRoutes,注意這裡有三種狀態在後面會用到,分別是null, router, undefined,為null則代表這個routeName配置過,但是不是子路由,router則代表是子路由,undefined代表沒有在路由配置中。
第二步:初始化路由棧
// Set up the initial state if needed
if (!state) {
//當state不存在時,初始化路由棧
console.log('開始初始化初始路由為' + initialRouteName + '的路由狀態...');
let route = {};
if (
action.type === NavigationActions.NAVIGATE &&
childRouters[action.routeName] !== undefined
) {
//這是一種配置導航首頁的寫法,首頁有三種寫法,第一種是routeConfig的第一項,第二種是stackConfig中指定initialRouteName,第三種則是routeName與在父路由中註冊的routeName一致,則為首頁。
//這也是navigate是使用action.action會被呼叫的地方,後邊會提到
return {
index: 0,
routes: [
{
...action,
type: undefined,
key: `Init-${_getUuid()}`,
},
],
};
}
if (initialChildRouter) {
//如果初始化路由為子路由,則預設以initialRouteName為首頁初始化子路由狀態
console.log('初始化路由為子路由時,獲取子路由的初始路由');
route = initialChildRouter.getStateForAction(
NavigationActions.navigate({
routeName: initialRouteName,
params: initialRouteParams,
})
);
console.log(initialRouteName + '的初始路由為:' + JSON.stringify(route))
}
//裝配params和route
const params = (route.params ||
action.params ||
initialRouteParams) && {
...(route.params || {}),
...(action.params || {}),
...(initialRouteParams || {}),
};
route = {
...route, //將子路由嵌入進來
routeName: initialRouteName,
key: `Init-${_getUuid()}`,
...(params ? { params } : {}),
};
// eslint-disable-next-line no-param-reassign
//裝配state
state = {
index: 0,
routes: [route],
};
console.log('初始路由為' + initialRouteName + '的路由狀態為:' + JSON.stringify(state));
}複製程式碼
這裡舉個例子,大家慢慢領悟,路由配置為:
const InvestScreen = TabRouter({
WanjiaJX: {
screen:WanjiaJX,
},
WanjiaY: {
screen: WanjiaYing,
},
}, {
initialRouteName: 'WanjiaY',
})
const MainTabNavigator = TabNavigator({
Home: {
screen: HomeScreen,
},
Invest: {
screen: InvestScreen,
},
Find: {
screen: FindScreen,
},
My: {
screen: MyScreen,
},
})
const CardStackNavigator = StackNavigator({
MainTab: {
screen: MainTabNavigator,
},
transferDetail: {
screen: TransferDetailScreen,
}
});
const ModelStackNavigator = StackNavigator({
mainStackNavigator: {
screen: CardStackNavigator,
},
investSuccess: {
screen: InvestSuccessScreen,
},
rechargeGetVcode: {
screen: RechargeGetVcodeScreen,
},
})
export default ModelStackNavigator複製程式碼
列印結果為:
開始解析路由配置...
StackRouter.js:42 {"MainTab":{},"transferDetail":{}}
StackRouter.js:55 路由配置解析結果:
StackRouter.js:56 {"MainTab":{},"transferDetail":null}
StackRouter.js:41 開始解析路由配置...
StackRouter.js:42 {"mainStackNavigator":{},"investSuccess":{},"rechargeGetVcode":{}}
StackRouter.js:55 路由配置解析結果:
StackRouter.js:56 {"mainStackNavigator":{},"investSuccess":null,"rechargeGetVcode":null}
StackRouter.js:105 開始初始化初始路由為mainStackNavigator的路由狀態...
StackRouter.js:125 初始化路由為子路由時,獲取子路由的初始路由
StackRouter.js:105 開始初始化初始路由為MainTab的路由狀態...
StackRouter.js:125 初始化路由為子路由時,獲取子路由的初始路由
StackRouter.js:132 MainTab的初始路由為:{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1}
StackRouter.js:152 初始路由為MainTab的路由狀態為:{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}]}
StackRouter.js:132 mainStackNavigator的初始路由為:{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}]}
StackRouter.js:152 初始路由為mainStackNavigator的路由狀態為:{"index":0,"routes":[{"index":0,"routes":[{"routes":[{"key":"Home","routeName":"Home"},{"routes":[{"key":"WanjiaJX","routeName":"WanjiaJX"},{"key":"WanjiaY","routeName":"WanjiaY"}],"index":1,"key":"Invest","routeName":"Invest"},{"key":"Find","routeName":"Find"},{"key":"My","routeName":"My"}],"index":1,"routeName":"MainTab","key":"Init-id-1499828145378-0"}],"routeName":"mainStackNavigator","key":"Init-id-1499828145378-1"}]}複製程式碼
用遞迴的方法從外到內獲取路由狀態,然後從內到外組裝路由狀態。
第三步:路由行為
前面分析了StackRoute的初始化,下面將依次來分析navigate,setParams,reset,goBack的行為。
react-navigation的Router的強大之處,也是難以理解之處就是路由的組合(composable)使用和相互巢狀。我們經常會使用navigate來push,使用goBack來pop,使用reset來重置路由棧,使用setParams來重置params。
關於路由巢狀和navigate action
在上面路由配置的示例中,我們提出兩個問題:
1、假如當前的頁面為:mainStackNavigator->mainTab->Invest->WanjiaY
,那在WanjiaYing的screen中,呼叫this.props.navigation.navigate('investSuccess')
,或者呼叫this.props.navigation.navigate('transferDetail')
會發生什麼?為什麼會有這種效果?
2、假如當前的頁面為:investSuccess
,在InvestSuccessScreen中呼叫this.props.navigation.navigate('My')
會有什麼效果?為什麼會有這種效果?
如果能回答以上兩個問題,那對於導航的巢狀和組合使用就瞭解了。我們先來分析程式碼,然後再給出答案。
第一段程式碼:
// Check if a child scene wants to handle the action as long as it is not a reset to the root stack
//只要不是對root stack的reset操作,都先檢查當前指定的子路由(使用key指定)或者activited狀態的子路由是否想處理改Action。
if (action.type !== NavigationActions.RESET || action.key !== null) {
//如果指定了key,則找到該key在state中的index,否則index為-1
const keyIndex = action.key
? StateUtils.indexOf(state, action.key)
: -1;
//當index < 0 時,則用activited狀態的index為childIndex,否則用keyIndex作為childIndex
const childIndex = keyIndex >= 0 ? keyIndex : state.index;
//通過childIndex找到childRoute
const childRoute = state.routes[childIndex];
//通過childRoute的routeNam在childRouter中查詢是否為子路由
const childRouter = childRouters[childRoute.routeName];
if (childRouter) {
//如果存在子路由,則讓子路由去處理這個Action,並且傳入對應的childRoute
const route = childRouter.getStateForAction(action, childRoute);
//如果route為null則返回當前state
if (route === null) {
return state;
}
//如果route不等於之前的childRoute,也就是route不等於preRoute
if (route && route !== childRoute) {
//在state的對應的key中做replace操作
return StateUtils.replaceAt(state, childRoute.key, route);
}
}
}複製程式碼
在上面程式碼中要好好理解兩個欄位,一個是childRouter
,一個是childRoute
,它們在字面上只有一個r
的區別,但是實際的用處是相差甚遠的,childRouter
是一個Router
,它就像StackRouter或者TabRouter一樣,擁有getStateForAction這些方法,childRoute
是一個Route
,它是存在於State
中的,它的結構類似於:
{
key: ***,
routeName: mainTab,
...
}複製程式碼
因此,在State中通過key找到Route,通過Route中的routeName在childRouter中找到是否有響應的router,來判斷它是否為子路由,這個邏輯就說的很通了。
所以這段程式碼的意思可以通俗的描述為:在非root stack的reset的action中,指定(通過key或者activited route)一個childRouter來處理action。
第二段程式碼:
// Handle explicit push navigation action
//處理確切的navigate action,所謂確切,就是說在routeConfig中有*直接*註冊過
if (
action.type === NavigationActions.NAVIGATE &&
childRouters[action.routeName] !== undefined
) {
const childRouter = childRouters[action.routeName];
let route;
if (childRouter) {
//如果navigate的是一個子路由,並且存在子action(action.action)則讓子路由執行這個子action
//如果沒有子action則使用init為action
const childAction =
action.action || NavigationActions.init({ params: action.params });
route = {
params: action.params,
...childRouter.getStateForAction(childAction), //注意,getStateForAction的第二個引數沒有傳,證明它是用來初始化狀態的。
key: _getUuid(),
routeName: action.routeName,
};
} else {
//沒有子路由則直接構建route
route = {
params: action.params,
key: _getUuid(),
routeName: action.routeName,
};
}
//直接push route
return StateUtils.push(state, route);
}複製程式碼
這段程式碼就比較簡單了,在當前(不會去遞迴到子路由)路由配置中找是否有註冊過routeName的screen,如果有註冊,則分兩種情況,第一種,這個screen就是一個普通screen,直接構建route即可,第二種是,這個screen是一個navigator,初始化子路由狀態(使用action.action或者init)然後組裝route。最後都要將route push到state中去。
這裡有個問題,在
...childRouter.getStateForAction(childAction)
這句程式碼中,如果childRouter為StackRouter,則會呼叫到第二步:初始化路由棧中的下面這段程式碼來:
if ( action.type === NavigationActions.NAVIGATE && childRouters[action.routeName] !== undefined ) { //這是一種配置導航首頁的寫法,首頁有三種寫法,第一種是routeConfig的第一項,第二種是stackConfig中指定initialRouteName,第三種則是routeName與在父路由中註冊的routeName一致,則為首頁。 return { index: 0, routes: [ { ...action, type: undefined, key: `Init-${_getUuid()}`, }, ], }; }複製程式碼
在這段程式碼中,如果
action.routeName
的childRouter依然是一個子路由,即childRouters[action.routeName] !== null
則會報錯,因為CardStack要渲染的screen為一個navigator,但是state中卻沒有相對應的routes和index。報錯資訊:Expect nav state to have routes and index, {"routeName": "MainTab", "key": "Init-id-123322334-3"}
改成如下即可:
if ( action.type === NavigationActions.NAVIGATE && childRouters[action.routeName] !== undefined ) { if(childRouters[action.routeName]) { const childRouter = childRouters[action.routeName]; state = { index: 0, routes: [ { ...action, ...childRouter.getStateForAction(action), type: undefined, key: `Init-${_getUuid()}`, }, ], }; console.log('返回狀態:' + JSON.stringify(state)); return state; } state = { index: 0, routes: [ { ...action, type: undefined, key: `Init-${_getUuid()}`, }, ], }; console.log('返回狀態:' + JSON.stringify(state)); return state; }複製程式碼
第三段程式碼:
//當指定的子路由沒有處理,路由配置中沒有配置響應的routeName時,遍歷*所有*子路由,一旦有子路由願意處理該action,則將處理結果push返回。
if (action.type === NavigationActions.NAVIGATE) {
const childRouterNames = Object.keys(childRouters);
for (let i = 0; i < childRouterNames.length; i++) {
const childRouterName = childRouterNames[i];
const childRouter = childRouters[childRouterName];
if (childRouter) {
//遍歷子路由,從初始狀態開始,處理Action
const initChildRoute = childRouter.getStateForAction(
NavigationActions.init()
);
//檢查子路由是否想處理action
const navigatedChildRoute = childRouter.getStateForAction(
action,
initChildRoute
);
let routeToPush = null;
if (navigatedChildRoute === null) {
// Push the route if the router has 'handled' the action and returned null
//如果子路由處理了這個action,並且返回null,則push子路由的初始狀態
routeToPush = initChildRoute;
} else if (navigatedChildRoute !== initChildRoute) {
//如果子路由處理了這個action,並改變了初始狀態,則push這個新的路由狀態
routeToPush = navigatedChildRoute;
}
if (routeToPush) {
return StateUtils.push(state, {
...routeToPush,
key: _getUuid(),
routeName: childRouterName,
});
}
}
}
}複製程式碼
總結:
以上分析了路由是如何管理子路由的,在處理action時會先判斷該action不是root stack的reset操作時(action.type !== NavigationActions.RESET || action.key !== null
),找到指定的router(找到action.key對應的router,當action.key無效時找到activited router)去處理該action,如果指定的router處理了這個action(返回null或者route!==childRoute)則在state中替換對應的route。
如果指定(通過key或者activited router)的router不處理action,則判斷action.routeName
有沒有在routeConfig中註冊過,對於這種直接註冊的,就直接push就好。
如果action.routeName
沒有被註冊過,則遍歷所有子路由去嘗試處理action,一旦有子路由去處理了,則直接push這個處理結果。
所以,你能回答上面兩個問題了嗎?
navigation action: setParams、reset、goBack
setParams:
程式碼:
if (action.type === NavigationActions.SET_PARAMS) {
//通過action.key在state中找到對應的route
const lastRoute = state.routes.find(
/* $FlowFixMe */
(route: *) => route.key === action.key
);
if (lastRoute) {
//如果route存在,將引數合併
const params = {
...lastRoute.params,
...action.params,
};
//做這一步是為了改變route的引用
const routes = [...state.routes];
routes[state.routes.indexOf(lastRoute)] = {
...lastRoute,
params,
};
//返回一個全新的state
return {
...state,
routes,
};
}
}複製程式碼
用法:
import { NavigationActions } from 'react-navigation'
const setParamsAction = NavigationActions.setParams({
params: { title: 'Hello' },
key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)複製程式碼
reset:
程式碼:
if (action.type === NavigationActions.RESET) {
const resetAction: NavigationResetAction = action;
return {
...state,
routes: resetAction.actions.map( //遍歷action.actions
(childAction: NavigationNavigateAction) => {
const router = childRouters[childAction.routeName];
if (router) {
//當childAction.routerName為子路由時,獲取子路由的初始狀態
return {
...childAction,
...router.getStateForAction(childAction), //這裡沒傳第二個引數,是去獲取初始狀態
routeName: childAction.routeName,
key: _getUuid(),
};
}
//直接建立route
const route = {
...childAction,
key: _getUuid(),
};
delete route.type;
return route;
}
),
index: action.index,
};
}複製程式碼
用法:
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0, //確定route的index
actions: [ //確定routes
NavigationActions.navigate({ routeName: 'Profile'})
]
})
this.props.navigation.dispatch(resetAction)複製程式碼
goBack:
程式碼:
if (action.type === NavigationActions.BACK) {
//當前要pop的index
let backRouteIndex = null;
if (action.key) {
//通過key找到route,通過route找到index
const backRoute = state.routes.find(
/* $FlowFixMe */
(route: *) => route.key === action.key
);
/* $FlowFixMe */
//賦值當前要pop的index
backRouteIndex = state.routes.indexOf(backRoute);
}
if (backRouteIndex == null) {
//當index不存在時,直接pop最上面的route
return StateUtils.pop(state);
}
if (backRouteIndex > 0) {
//pop route到index為backRouteIndex - 1
return {
...state,
routes: state.routes.slice(0, backRouteIndex),
index: backRouteIndex - 1,
};
}
}
return state;
},複製程式碼
用法:
import { NavigationActions } from 'react-navigation'
const backAction = NavigationActions.back({
key: 'Profile'
})
this.props.navigation.dispatch(backAction)複製程式碼
注意:setParams、reset、goBack都是可以通過key來使用的,可以自動在一個conatiner navigtor中指定router來處理這些action。在StackRouter中key是使用_getUuid直接生成的,可以用過this.props.navigation.state.key獲取到。
總結
專案中使用了redux + react-navigation,還是覺得很好用的,但是剛開始學習時感覺與以前使用過的navigation在思想上有種種不同,很多時候想自定義或者修改時常常找不到地方,比如防止push同樣的screen,指定routeName來back等,但是多看文件和原始碼後,發現它的自由度是非常高的,可以重寫或攔截router,自定義NavigatorView,靈活的配置Transition等,配合redux也是非常好用的。值得推。
其實這些官方文件都有所描述,只是之前看的雲裡霧裡,現在終有所理解,希望對和我一樣在使用react-navigation時有疑問的同學有所幫助。
歡迎關注我的簡書主頁:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^