React Navigation 的我的分析與融合

分析 React Navigation:(不是教程)

Learn once, navigate anywhere.react

React Native 官方推薦的一種路由模塊,其自己主要包含三個部分:git

  • The Navigation Prop
  • Router
  • View

The Navigation Prop 主要用於 Action 的分發,這部分在後面討論。咱們首先根據 RouterView 分析一下模塊的內置導航器(Navigator)。github

Router

Router 能夠認爲是 React Navigation 模塊的 reducer , 具體的路由操做和響應是由她來完成的。開發人員經過對 Router 的訂製來實現路由的特殊操做,如官網給出 阻止修改中模塊的路由 實例。這裏須要指出的是 Router 是組件的靜態屬性,當使用高價組件時,注意使用 hoist-non-react-statics 將靜態屬性和方法複製到高階組件上,固然也可使用 React Navigation 給出的 WithNavigation 方法。React Navigation 模塊內置的 Router 分爲:redux

  • StackRouter
  • TabRouter

View

View 則是 React Navigation 模塊的展現組件,她經過 The Navigation PropRouter 所提供的屬性顯示相關內容。React Navigation 內置的 View 分爲:函數

  • CardStack
  • Tabs
  • Drawer

根據上述內置 RouterView 的排列組合,React Navigation
模塊對外給出了三種導航器(Navigator)ui

  • StackNavigator
    • StackRouter
    • CardStack
  • TabNavigator
    • TabRouter
    • CardStack
    • Tabs
  • DrawerNavigator
    • StackRouter
    • Drawer

Navigation Props

有了 reducer,有了 展現組件,那麼確定也有觸發狀態改變的 Action 和 發送 Action 的方法。React Navigation 給出了五種 Actions:this

  • Navigate
  • Reset
  • Back
  • Set Params
  • Init

與此對應的方法分別是:spa

  • navigate
  • setParams
  • goBack

可是上述方法都是輔助函數,是由 Navigation Props
中的 dispatchstate 屬性生成的。 dispatch ??? Actions???看來 React Navigation 模塊天生和 Redux 兼容,事實也確實如此,咱們只須要將 Redux 中的 dispatchstate 的路由部分分別賦值給 Navigation Propsdispatchstate,而後使用 React Navigation 給出的 addNavigationHelpers就能夠很方便的生成上述發送 Action 的方法,最後在 Redux 中定義路由的 reducer 就完成了路由狀態和 Redux 結合。給出官方的實例:code

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:

我的項目能不造輪子就儘可能不造了(也沒那水平)。主要使用的模塊有:orm

  • 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 時,每次發送 back 路由 Action 時,都出現問題。查看發現每次銷燬表單後,redux-form 又自動註冊了表單,看來是誰又觸發了 redux-form,最終發現是因爲和路由 reducer 衝突,由於 Action 沒有加限制,每次都會執行路由 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 結合之前的狀態機思想,實現了將反作用狀態包含路由狀態都封裝在 saga 中:

// 登陸狀態機
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'
    }
  }
}
// 狀態對應的 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()
}
// 有限狀態自動機
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 }
}
// 生成反作用對應的狀態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 的使用,才讓我感到路由結合進 redux 中的必要性。固然對你來講也許不一樣,請留言指教指正。

相關文章
相關標籤/搜索