React Navigationの個人分析と融合

8300 ワード

分析React Navigation:(チュートリアルではありません)Learn once, navigate anywhere.
React Nativeが公式に推奨するルーティングモジュールで、それ自体は主に3つの部分を含む.
  • The Navigation Prop
  • Router
  • View
  • The Navigation Propは主にActionの配布に用いられ、この部分は後述する.まず,RouterViewに基づいてモジュールの内蔵ナビゲーション(Navigator)を解析する.
    Router RouterはReact Navigationモジュールのreducerと考えられ、具体的なルーティング動作および応答は彼女によって行われる.開発者は、Routerのカスタマイズによって、公式サイトが修正中のモジュールをブロックするルーティングの例を示すように、ルーティングの特殊な動作を実現する.ここで指摘する必要があるのはRouterがコンポーネントの静的属性であり、高価なコンポーネントを使用する場合、hoist-non-react-staticsを使用して静的属性および方法を高次コンポーネントにコピーすることに注意し、もちろんReact Navigationが与えたWithNavigationの方法を使用することもできる.React Navigationモジュールに内蔵されているRouterは、次のように分けられます.
  • StackRouter
  • TabRouter

  • View Viewは、The Navigation PropおよびRouterによって提供される属性によって関連コンテンツを表示するReact Navigationモジュールのディスプレイコンポーネントである.React Navigationに内蔵されているViewは、次のように分けられます.
  • CardStack
  • Tabs
  • Drawer

  • React Navigationモジュールは、上記のRouterおよびViewを内蔵する配列の組み合わせに従って、3つのナビゲーション(Navigator)を外部に提供する.
  • StackNavigator
  • StackRouter
  • CardStack

  • TabNavigator
  • TabRouter
  • CardStack
  • Tabs

  • DrawerNavigator
  • StackRouter
  • Drawer


  • Navigation Props
    reducerがあり,展示コンポーネントがあれば,状態変化のActionをトリガし,Actionを送信する方法もあるに違いない.React Navigationは5つのActionsを与えた.
  • Navigate
  • Reset
  • Back
  • Set Params
  • Init

  • これに対応する方法は、次のとおりです.
  • navigate
  • setParams
  • goBack

  • しかし、上記の方法はいずれも補助関数であり、Navigation Propsdispatchstateの属性によって生成される.dispatch ??? Actions???React Navigationモジュールは生まれつきReduxと互換性があるように見えますが、実際には、Reduxのdispatchstateのルーティング部分をそれぞれNavigation Propsdispatchstateに割り当て、React Navigationが与えたaddNavigationHelpersを使用するだけで、上記の送信Actionの生成方法を容易にすることができます.最後にReduxでルーティングを定義するreducerはルーティング状態とRedux結合を完了する.公式の例を示します.
    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 (
          
        )
      }
    }
    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
    まず、immutableを適用するためにルーティングのreducerを改造します.
    const navReducer = (state = initialState, action) => {
      const nextState = fromJS(AppStackNavigator.router.getStateForAction(action, state.toJS()))
      return nextState || state
    }
    

    redux-form
    その後、redux−formを使用すると、backルーティングアクションが送信されるたびに問題が発生する.フォームが破棄されるたびに、redux-formは自動的にフォームに登録され、誰がredux-formをトリガーしたのかを確認します.最終的には、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でのルーティングアクションの使用に至るまで、ルーティングがreduxに結合する必要性を感じさせます.もちろんあなたにとっては違うかもしれませんが、コメントを残して指摘してください.