分析 React Navigation:(不是教程)
Learn once, navigate anywhere.
React Native 官方推薦的一種路由模塊硝拧,其本身主要包含三個部分:
The Navigation Prop
Router
View
The Navigation Prop
主要用于 Action 的分發(fā)幔嗦,這部分在后面討論暇昂。我們首先根據(jù) Router
和 View
分析一下模塊的內(nèi)置導(dǎo)航器(Navigator)冷冗。
Router
Router
可以認(rèn)為是 React Navigation 模塊的 reducer , 具體的路由操作和響應(yīng)是由她來完成的。開發(fā)人員通過對 Router
的訂制來實現(xiàn)路由的特殊操作荣暮,如官網(wǎng)給出 阻止修改中模塊的路由 實例进副。這里需要指出的是 Router
是組件的靜態(tài)屬性,當(dāng)使用高價組件時悬槽,注意使用 hoist-non-react-statics 將靜態(tài)屬性和方法復(fù)制到高階組件上怀吻,當(dāng)然也可以使用 React Navigation 給出的 WithNavigation
方法。React Navigation 模塊內(nèi)置的 Router
分為:
StackRouter
TabRouter
View
View
則是 React Navigation 模塊的展示組件初婆,她通過 The Navigation Prop
和 Router
所提供的屬性顯示相關(guān)內(nèi)容蓬坡。React Navigation 內(nèi)置的 View
分為:
CardStack
Tabs
Drawer
根據(jù)上述內(nèi)置 Router
和 View
的排列組合猿棉,React Navigation
模塊對外給出了三種導(dǎo)航器(Navigator)
-
StackNavigator
StackRouter
CardStack
-
TabNavigator
TabRouter
CardStack
Tabs
-
DrawerNavigator
StackRouter
Drawer
Navigation Props
有了 reducer,有了 展示組件屑咳,那么肯定也有觸發(fā)狀態(tài)改變的 Action 和 發(fā)送 Action 的方法萨赁。React Navigation 給出了五種 Actions:
Navigate
Reset
Back
Set Params
Init
與此對應(yīng)的方法分別是:
navigate
setParams
goBack
但是上述方法都是輔助函數(shù),是由 Navigation Props
中的 dispatch
和 state
屬性生成的兆龙。 dispatch
杖爽??紫皇? Actions慰安??聪铺?看來 React Navigation 模塊天生和 Redux 兼容化焕,事實也確實如此,我們只需要將 Redux 中的 dispatch
和 state
的路由部分分別賦值給 Navigation Props
的 dispatch
和 state
铃剔,然后使用 React Navigation 給出的 addNavigationHelpers
就可以很方便的生成上述發(fā)送 Action 的方法撒桨,最后在 Redux 中定義路由的 reducer 就完成了路由狀態(tài)和 Redux 結(jié)合。給出官方的實例:
const AppNavigator = StackNavigator(AppRouteConfigs)
// 此 reducer 與部分模塊沖突番宁,需要在以后修改
const navReducer = (state = initialState, action) => {
const nextState = AppNavigator.router.getStateForAction(action, state)
return nextState || state
}
// 根展示組件
class App extends React.Component {
render() {
return (
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
)
}
}
const mapStateToProps = (state) => ({
nav: state.nav
})
// 控制組件
const AppWithNavigationState = connect(mapStateToProps)(App);
融合 React Navigation:
個人項目能不造輪子就盡量不造了(也沒那水平)元莫。主要使用的模塊有:
- react native
- redux、react-redux蝶押、redux-immutable
- redux-saga
- redux-form
- immutable.js
- reselect
immutable
首先改造路由的 reducer 以適用 immutable:
const navReducer = (state = initialState, action) => {
const nextState = fromJS(AppStackNavigator.router.getStateForAction(action, state.toJS()))
return nextState || state
}
redux-form
隨后在使用 redux-form 時踱蠢,每次發(fā)送 back
路由 Action 時,都出現(xiàn)問題棋电。查看發(fā)現(xiàn)每次銷毀表單后茎截,redux-form 又自動注冊了表單,看來是誰又觸發(fā)了 redux-form赶盔,最終發(fā)現(xiàn)是由于和路由 reducer 沖突企锌,因為 Action 沒有加限制,每次都會執(zhí)行路由 reducer 于未,將其改為:
const initialNavState = AppStackNavigator.router.getStateForAction(
NavigationActions.init()
)
const navReducer = (state = fromJS(initialNavState), action) => {
if (
action.type === NavigationActions.NAVIGATE ||
action.type === NavigationActions.BACK ||
action.type === NavigationActions.RESET ||
action.type === NavigationActions.INIT ||
action.type === NavigationActions.SET_PARAMS ||
action.type === NavigationActions.URI
) {
console.log(action)
return fromJS(AppStackNavigator.router.getStateForAction(action, state.toJS()))
} else {
return state
}
}
export default navReducer
redux-saga
redux-saga 中使用 NavigationActions 結(jié)合以前的狀態(tài)機思想撕攒,實現(xiàn)了將副作用狀態(tài)包含路由狀態(tài)都封裝在 saga 中:
// 登錄狀態(tài)機
const machineState = {
currentState: 'login_screen',
states: {
login_screen: {
login: 'loading'
},
loading: {
success: 'main_screen',
failure: 'error'
},
main_screen: {
logout: 'login_screen',
failure: 'error'
},
error: {
login_retry: 'login_screen',
logout_retry: 'main_screen'
}
}
}
// 狀態(tài)對應(yīng)的 effects
function * clearError() {
yield delay(2000)
yield put({ type: REQUEST_ERROR, payload: '' })
}
function * mainScreenEffects() {
yield put({ type: SET_AUTH, payload: true })
yield put(NavigationActions.back())
yield put({ type: SET_LOADING, payload: { scope: 'login', loading: false } })
}
function * errorEffects(error) {
yield put({ type: REQUEST_ERROR, payload: error.message })
yield put({ type: SET_LOADING, payload: { scope: 'login', loading: false } })
yield fork(clearError)
}
function * loginEffects() {
yield put({ type: SET_AUTH, payload: false })
yield put(NavigationActions.reset({
index: 1,
actions: [
NavigationActions.navigate({ routeName: 'Main' }),
NavigationActions.navigate({ routeName: 'Login' })
]
})) // Redirect to the login page
}
const effects = {
loading: () =>
put({
type: SET_LOADING,
payload: { scope: 'login', loading: true }
}),
main_screen: () => mainScreenEffects(),
error: error => errorEffects(error),
login_screen: () => loginEffects()
}
// 有限狀態(tài)自動機
const Machine = (state, effects) => {
let machineState = state
function transition(state, operation) {
const currentState = state.currentState
const nextState = state.states[currentState][operation]
? state.states[currentState][operation]
: currentState
return { ...state, currentState: nextState }
}
function operation(name) {
machineState = transition(machineState, name)
}
function getCurrentState() {
return machineState.currentState
}
const getEffect = name => (...arg) => {
operation(name)
return effects[machineState.currentState](...arg)
}
return { operation, getCurrentState, getEffect }
}
// 生成副作用對應(yīng)的狀態(tài)effects
const machine = Machine(machineState, effects)
const loginEffect = machine.getEffect('login')
const failureEffect = machine.getEffect('failure')
const successEffect = machine.getEffect('success')
const logoutEffect = machine.getEffect('logout')
//登錄和登出流程
export function * loginFlow(): any {
while (true) {
const action: { type: string, payload: Immut } = yield take(LOGIN_REQUEST)
const username: string = action.payload.get('username')
const password: string = action.payload.get('password')
yield loginEffect()
try {
let isAuth: ?boolean = yield call(Api.login, { username, password })
if (isAuth) {
yield successEffect()
}
} catch (error) {
yield failureEffect(error)
machine.operation('login_retry')
}
}
}
export function * logoutFlow(): any {
while (true) {
yield take(LOGOUT_REQUEST)
try {
let isLogout: ?boolean = yield call(Api.logout)
if (isLogout) {
yield logoutEffect()
}
} catch (error) {
yield failureEffect(error)
machine.operation('logout_retry')
}
}
}
直到 redux-saga 中路由 Action 的使用,才讓我感到路由結(jié)合進 redux 中的必要性烘浦。當(dāng)然對你來說也許不同抖坪,請留言指教指正。