React Navigation 的個人分析與融合

分析 React Navigation:(不是教程)

Learn once, navigate anywhere.

React Native 官方推薦的一種路由模塊硝拧,其本身主要包含三個部分:

  • The Navigation Prop
  • Router
  • View

The Navigation Prop 主要用于 Action 的分發(fā)幔嗦,這部分在后面討論暇昂。我們首先根據(jù) RouterView 分析一下模塊的內(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 PropRouter 所提供的屬性顯示相關(guān)內(nèi)容蓬坡。React Navigation 內(nèi)置的 View 分為:

  • CardStack
  • Tabs
  • Drawer

根據(jù)上述內(nèi)置 RouterView 的排列組合猿棉,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
中的 dispatchstate 屬性生成的兆龙。 dispatch 杖爽??紫皇? Actions慰安??聪铺?看來 React Navigation 模塊天生和 Redux 兼容化焕,事實也確實如此,我們只需要將 Redux 中的 dispatchstate 的路由部分分別賦值給 Navigation Propsdispatchstate铃剔,然后使用 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)然對你來說也許不同抖坪,請留言指教指正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闷叉,一起剝皮案震驚了整個濱河市擦俐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌握侧,老刑警劉巖蚯瞧,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘿期,死亡現(xiàn)場離奇詭異,居然都是意外死亡埋合,警方通過查閱死者的電腦和手機备徐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚颂,“玉大人坦喘,你說我怎么就攤上這事∥魃瑁” “怎么了瓣铣?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贷揽。 經(jīng)常有香客問我棠笑,道長,這世上最難降的妖魔是什么禽绪? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任蓖救,我火速辦了婚禮,結(jié)果婚禮上印屁,老公的妹妹穿的比我還像新娘循捺。我一直安慰自己,他們只是感情好雄人,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布从橘。 她就那樣靜靜地躺著,像睡著了一般础钠。 火紅的嫁衣襯著肌膚如雪恰力。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天旗吁,我揣著相機與錄音踩萎,去河邊找鬼。 笑死很钓,一個胖子當(dāng)著我的面吹牛香府,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播码倦,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼企孩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了叹洲?” 一聲冷哼從身側(cè)響起柠硕,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤工禾,失蹤者是張志新(化名)和其女友劉穎运提,沒想到半個月后蝗柔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡民泵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年癣丧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栈妆。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡胁编,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鳞尔,到底是詐尸還是另有隱情嬉橙,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布寥假,位于F島的核電站市框,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糕韧。R本人自食惡果不足惜枫振,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萤彩。 院中可真熱鬧粪滤,春花似錦、人聲如沸雀扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愚墓。三九已至窍侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間转绷,已是汗流浹背伟件。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留议经,地道東北人斧账。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像煞肾,于是被迫代替她去往敵國和親咧织。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容