在上一篇介紹了React Native開發環境搭建,咱們已經能夠在本地成功運行一個helloword應用了,本節將開始詳細分析如何搭建一個React Native App應用架構,並支持完整本地運行預覽。javascript
完整代碼見githubhtml
如今已經有不少腳手架工具,如ignite,支持一鍵建立一個React Native App項目結構,很方便,可是享受方便的同時,也失去了對項目架構及技術棧完整學習的機會,並且一般腳手架建立的應用技術架構並不能徹底知足咱們的業務需求,須要咱們本身修改,完善,因此若是但願對項目架構有更深掌控,最好仍是從0到1理解一個項目。java
首先使用react-native-cli
工具建立一個React Native應用:node
react-native init fuc
生成項目結構以下圖:react
package.json
爲項目依賴管理文件;index.ios.js
爲ios平臺入口文件,index.android.js
爲android平臺入口文件,一般用來註冊React Native App根組件;.babelrc
文件,babel的配置文件,React Native默認使用babel編譯JavaScript代碼;__tests__
項目測試目錄。咱們看到並無存放React Native原生JavaScript代碼的目錄,這須要咱們本身進行建立了,一般建立一個src
目錄做爲App應用Javascript部分全部代碼和資源的根目錄,一個src/constants
目錄以保存全局共享常量數據,一個src/config
目錄保存全局配置,一個src/helpers
存放全局輔助,工具類方法,一個src/app.js
做爲RN部分入口文件,另外一般還須要建立保存各模塊redux的目錄,redux中間件的目錄等。android
項目架構搭建很大部分依賴於項目的技術棧,因此先對整個技術棧進行分析,總結:ios
根據以上劃分決定選用如下第三方庫和工具構成項目的完整技術棧:git
針對以上分析,完善後的項目結構如圖:github
如上圖,在項目根目錄下建立src
目錄,而在src目錄中依次建立12個目錄與1個React Native部分入口js文件。
React Native App開發目前已經有諸多調試工具,經常使用的如atom和Nuclide,移動端模擬器自帶的調試工具,Reactron等。
Nuclide是由Facebook提供的基於atom的集成開發環境,可用於編寫、運行和 調試React Native應用。
在模擬器啓動運行App後,瀏覽器會自動打開 http://localhost:8081/debugger-ui
頁,能夠在控制檯進行js調試輸出及遠端js斷點調試;在模擬器終端使用快捷鍵command
加D
鍵便可打開調試工具,包括從新加載應用,開啓熱加載,切換DOM審視器等:
Reactotron是一款跨平臺調試React及React Native應用的桌面應用,能動態實時監測並輸出React應用等redux,action,saga異步請求等信息,如圖:
首先初始化Reactotron相關配置:
import Config from './DebugConfig'; import Immutable from 'immutable'; import Reactotron from 'reactotron-react-native'; import { reactotronRedux as reduxPlugin } from 'reactotron-redux'; import sagaPlugin from 'reactotron-redux-saga'; if (Config.useReactotron) { // refer to https://github.com/infinitered/reactotron for more options! Reactotron .configure({ name: 'Os App' }) .useReactNative() .use(reduxPlugin({ onRestore: Immutable })) .use(sagaPlugin()) .connect(); // Let's clear Reactotron on every time we load the app Reactotron.clear(); // Totally hacky, but this allows you to not both importing reactotron-react-native // on every file. This is just DEV mode, so no big deal. console.tron = Reactotron; }
而後啓使用console.tron.overlay
方法拓展入口組件:
import './config/ReactotronConfig'; import DebugConfig from './config/DebugConfig'; class App extends Component { render () { return ( <Provider store={store}> <AppContainer /> </Provider> ) } } // allow reactotron overlay for fast design in dev mode export default DebugConfig.useReactotron ? console.tron.overlay(App) : App
至此就可使用Reactotron客戶端捕獲應用中發起的全部的redux和action了。
React Native應用依然遵循React組件化開發原則,組件負責渲染UI,組件不一樣狀態對應不一樣UI,一般遵循如下組件設計思路:
展現型組件 | 容器組件 | |
---|---|---|
目標 | UI展現 (HTML結構和樣式) | 業務邏輯(獲取數據,更新狀態) |
感知Redux | 無 | 有 |
數據來源 | props | 訂閱Redux store |
變動數據 | 調用props傳遞的回調函數 | Dispatch Redux actions |
可重用 | 獨立性強 | 業務耦合度高 |
建立跨平臺應用時,雖然React Native作了大量跨平臺兼容的工做,可是依然存在一些須要爲不一樣平臺開發不一樣代碼的狀況,這時候須要額外處理。
咱們能夠將不一樣平臺代碼文件以不一樣目錄區分開來,如:
/common/components/ /android/components/ /ios/components/
common
目錄下存放公用文件,android
目錄存放android文件代碼,ios
存放ios文件代碼,可是一般都選擇React Native提供的更好方式,後文介紹。
React Native內置了一個Platform模塊,用以區分應用當前運行平臺,當運行在ios平臺下時,Platform.OS
值爲ios
,運行在android平臺下則爲android
,能夠利用此模塊加載對應平臺文件:
var StatusBar = Platform.select({ ios: () => require('ios/components/StatusBar'), android: () => require('android/components/StatusBar'), })();
而後正常使用該StatusBar組件便可。
當引用某組件時,React Native會檢測該文件是否存在.android
或.ios
後綴,若是存在則根據當前平臺加載對應文件組件,如:
StatusBar.ios.js StatusBar.indroid.js
同一目錄下存在以上兩個文件,則可使用如下方式引用:
import StatusBar from './components/StatusBar';
React將會根據當前平臺加載對應後綴文件,推薦使用此方式作平臺組件級代碼適配,而對於局部小部分須要適配平臺的代碼可使用Platform.OS
值,以下,若僅僅須要在ios平臺下添加一個更高的margin-top值且不是公用樣式時:
var styles = StyleSheet.create({ marginTop: (Platform.OS === 'ios') ? 20 : 10, });
不一樣於React應用的單頁面路由,React Native一般都是多頁面形式存在,以導航方式在不一樣頁面和組件間切換,而不是路由方式控制不一樣組件展現,最常使用的是react-navigation導航庫。
在React web應用中,頁面UI組件展現和切換徹底由路由控制,每個路由都有對應的URL及路由信息,在React Native應用則不是由路由驅動組件展現,而是由導航控制切換屏展現,每一屏有各自的路由信息。
或許你已經依賴react-router的單頁面應用路由配置方式,但願建立一個Url驅動的跨平臺App應用,託福於活躍的的開源社區,你可使用react-router-native,可是並不推薦,由於對於App而言,從交互和體驗考慮,仍是更適合使用多頁面(屏)形式。
使用react-navigation能夠定義跨平臺的應用導航結構,也支持配置渲染跨平臺的導航欄,tab欄等組件。
react-navigation提供如下幾個方法支持建立不一樣的導航類型:
StackNavigator支持跨平臺以變換方式切換不一樣屏,而且將當前屏放置在棧頂,調用方式以下:
StackNavigator(RouteConfigs, StackNavigatorConfig)
導航棧路由(route)配置對象,定義route名和route對象,該route對象定義當前路由對應的展現組件,如:
// routes爲路由信息對象 StackNavigator({ [routes.Main.name]: Main, [routes.Login.name]: { path: routes.Login.path, screen: LoginContainer, title: routes.Login.title } }
如上,代表當應用導航至路由routes.Login.name
時,渲染LoginContainer
組件,由對象screen屬性指定;而導航至路由routes.Main.name
值時,對應渲染MainStack,代碼中Main
對象爲:
{ path: routes.Main.path, screen: MainStack, navigationOptions: { gesturesEnabled: false, }, }
而MainStack是一個Stacknavigator:
const MainStack = StackNavigator({ Home: HomeTabs })
HomeTabs是一個TabNavigator:
{ name: 'Home Tabs', description: 'Tabs following Home Tabs', headerMode: 'none', screen: HomeTabs };
路由配置對象,能夠選擇性配置可選屬性,如:
initialRouteName
,初始導航棧默認屏,必須是路由配置對象中的某一鍵名;initialRouteParams
,初始路由的默認參數;navigationOptions
,設置默認的導航屏配置;
headerMode
,是否顯示頂部導航欄:
mode
,導航切換屏時的樣式和變換效果:
card
:默認方式,標準的屏變換;modal
:僅在ios平臺有效,使屏幕底部滑出新屏;{ initialRouteName: routes.Login.name, headerMode: 'none', // 去除頂部導航欄 /** * Use modal on iOS because the card mode comes from the right, * which conflicts with the drawer example gesture */ mode: Platform.OS === 'ios' ? 'modal' : 'card' }
使用TabNavigator能夠建立一屏,擁有TabRouter能夠切換不一樣Tab,調用方式如:
TabNavigator(RouteConfigs, TabNavigatorConfig)
Tab路由配置對象,格式相似StackNavigator。
Tab導航相關配置對象,如:
TabBarBottom
組件,android平臺默認使用TabBarTop
組件;top
或bottom
;const HomeTabs = TabNavigator( { Notification: { screen: NotificationTabContainer, path: 'notification', navigationOptions: { title: '消息通知' } }, Myself: { screen: MyselfTabContainer, path: 'myself', navigationOptions: { title: '個人' } } }, { tabBarOptions: { activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff', }, swipeEnabled: true } );
使用DrawerNavigator能夠建立抽屜式導航屏,調用方式以下:
DrawerNavigator(RouteConfigs, DrawerNavigatorConfig)
const MyDrawer = DrawerNavigator({ Home: { screen: MyHomeDrawerScreen, }, Notifications: { screen: MyNotificationsDrawerScreen, }, });
抽屜式導航路由配置對象,格式相似StackNavigator。
抽屜式導航屏配置對象,如:
left
或right
;DrawerItems
;import { DrawerItems } from 'react-navigation'; const CustomDrawerContentComponent = (props) => ( <View style={styles.container}> <DrawerItems {...props} /> </View> ); const DrawerNavi = DrawerNavigator({}, { drawerWidth: 200, drawerPosition: 'right', contentComponent: props => <CustomDrawerContentComponent {...props}/>, drawerBackgroundColor: 'transparent' })
RN應用的每一屏將接受一個navigation屬性包含如下方法和屬性:
使用navigate方法導航至其餘屏:
navigate(routeName, params, action)
改變當前導航路由信息,如設置修改導航標題等信息:
class ProfileScreen extends React.Component { render() { const { setParams } = this.props.navigation; return ( <Button onPress={() => setParams({name: 'Jh'})} title="Set title" /> ) } }
從當前屏(參數爲空)或者指定屏(參數爲屏路由鍵名)導航回退至該屏的上一屏,而且關閉該屏;若傳遞null
參數,則未指定來源屏,即不會關閉屏。
每一屏都有本身的路由信息,能夠經過this.props.navigation.state
訪問,其返回數據格式如:
{ // the name of the route config in the router routeName: 'Login', //a unique identifier used to sort routes key: 'login', //an optional object of string options for this screen params: { user: 'jh' } }
該方法用來分發導航action至路由,實現導航,可使用react-navigation
默認提供的action建立函數NavigationActions
,以下爲分發一個navigate導航切換屏action:
import { NavigationActions } from 'react-navigation' const navigateAction = NavigationActions.navigate({ routeName: routeName || routes.Login.name, params: {}, // navigate can have a nested navigate action that will be run inside the child router action: NavigationActions.navigate({ routeName: 'Notification'}) }); // dispatch the action this.props.navigation.dispatch(navigateAction);
在使用Redux之後,須要遵循redux的原則:單一可信數據來源,即全部數據來源都只能是reudx store,Navigation路由狀態也不該例外,因此須要將Navigation state與store state鏈接,能夠建立一個Navigation reducer以合併Navigation state至store:
import AppNavigation from '../routes'; const NavigationReducer = (state = initialState, action) => { const newState = Object.assign({}, state, AppNavigation.router.getStateForAction(action, state)); return newState || state; }; export const NavigationReducers = { nav: NavigationReducer };
這個reducer所作的只是將App導航路由狀態合併入store。
現代的任何大型web應用若是少了狀態管理容器,那這個應用就缺乏了時代特徵,可選的庫諸如mobx,redux等,實際上大同小異,各取所需,以redux爲例,redux是最經常使用的react應用狀態容器庫,對於React Native應用也適用。
和React應用同樣,須要將Redux和應用鏈接起來,才能統一使用redux管理應用狀態,使用官方提供的react-redux庫。
class App extends Component { render () { return ( <Provider store={store}> <AppContainer /> </Provider> ) } }
使用redux提供的createStore
方法建立redux store,可是在實際項目中咱們經常須要拓展redux添加某些自定義功能或服務,如添加redux中間件,添加異步任務管理saga,加強redux等:
// creates the store export default (rootReducer, rootSaga, initialState) => { /* ------------- Redux Configuration ------------- */ const middleware = []; const enhancers = []; /* ------------- Analytics Middleware ------------- */ middleware.push(ScreenTracking); /* ------------- Saga Middleware ------------- */ const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null; const sagaMiddleware = createSagaMiddleware({ sagaMonitor }); middleware.push(sagaMiddleware); /* ------------- Assemble Middleware ------------- */ enhancers.push(applyMiddleware(...middleware)); /* ------------- AutoRehydrate Enhancer ------------- */ // add the autoRehydrate enhancer if (ReduxPersist.active) { enhancers.push(autoRehydrate()); } // if Reactotron is enabled (default for __DEV__), // we'll create the store through Reactotron const createAppropriateStore = Config.useReactotron ? console.tron.createStore : createStore; const store = createAppropriateStore(rootReducer, initialState, compose(...enhancers)); // configure persistStore and check reducer version number if (ReduxPersist.active) { RehydrationServices.updateReducers(store); } // kick off root saga sagaMiddleware.run(rootSaga); return store; }
redux默認提供了combineReducers
方法整合reduers至redux,然而該默認方法指望接受原生JavaScript對象而且它把state做爲原生對象處理,因此當咱們使用createStore
方法而且接受一個Immutable對象做應用初始狀態時,reducer
將會返回一個錯誤,源代碼以下:
if (!isPlainObject(inputState)) { return ( `The ${argumentName} has unexpected type of "` + ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + ".Expected argument to be an object with the following + `keys:"${reducerKeys.join('", "')}"` ) }
如上代表,原始類型reducer接受的state參數應該是一個原生JavaScript對象,咱們須要對combineReducers
其進行加強,以使其能處理Immutable對象,redux-immutable
即提供建立一個能夠和Immutable.js協做的Redux combineReducers。
import { combineReducers } from 'redux-immutable'; import Immutable from 'immutable'; import configureStore from './CreateStore'; // use Immutable.Map to create the store state tree const initialState = Immutable.Map(); export default () => { // Assemble The Reducers const rootReducer = combineReducers({ ...NavigationReducers, ...LoginReducers }); return configureStore(rootReducer, rootSaga, initialState); }
如上代碼,能夠看見咱們傳入的initialState
是一個Immutable.Map
類型數據,咱們將redux整個state樹叢根源開始Immutable化,另外傳入了能夠處理Immutable state的reducers和sagas。
另外每個state樹節點數據都是Immutable結構,如NavigationReducer
:
const initialState = Immutable.fromJS({ index: 0, routes: [{ routeName: routes.Login.name, key: routes.Login.name }] }); const NavigationReducer = (state = initialState, action) => { const newState = state.merge(AppNavigation.router.getStateForAction(action, state.toJS())); return newState || state; };
reducer默認state節點使用Immutable.fromJS()方法將其轉化爲Immutable結構,而且更新state時使用Immutable方法state.merge()
,保證狀態統一可預測。
咱們知道瀏覽器默認有資源的緩存功能而且提供本地持久化存儲方式如localStorage,indexDb,webSQL等,一般能夠將某些數據存儲在本地,在必定週期內,當用戶再次訪問時,直接從本地恢復數據,能夠極大提升應用啓動速度,用戶體驗更有優點,對於App應用而言,本地持久化一些啓動數據甚至離線應用更是常見的需求,咱們可使用AsyncStorage(相似於web的localStorage)存儲一些數據,若是是較大量數據存儲可使用SQLite。
另外不一樣於以往的直接存儲數據,啓動應用時本地讀取而後恢復數據,對於redux應用而言,若是隻是存儲數據,那麼咱們就得爲每個reducer拓展,當再次啓動應用時去讀取持久化的數據,這是比較繁瑣並且低效的方式,是否能夠嘗試存儲reducer key,而後根據key恢復對應的持久化數據,首先註冊Rehydrate reducer,當觸發action時根據其reducer key恢復數據,而後只須要在應用啓動時分發action,這也很容易抽象成可配置的拓展服務,實際上三方庫redux-persist已經爲咱們作好了這一切。
要實現redux的持久化,包括redux store的本地持久化存儲及恢復啓動兩個過程,若是徹底本身編寫實現,代碼量比較複雜,可使用開源庫redux-persist
,它提供persistStore
和autoRehydrate
方法分別持久化本地存儲store及恢復啓動store,另外還支持自定義傳入持久化及恢復store時對store state的轉換拓展。
以下在建立store時會調用persistStore相關服務-RehydrationServices.updateReducers()
:
// configure persistStore and check reducer version number if (ReduxPersist.active) { RehydrationServices.updateReducers(store); }
該方法內實現了store的持久化存儲:
// Check to ensure latest reducer version AsyncStorage.getItem('reducerVersion').then((localVersion) => { if (localVersion !== reducerVersion) { if (DebugConfig.useReactotron) { console.tron.display({ name: 'PURGE', value: { 'Old Version:': localVersion, 'New Version:': reducerVersion }, preview: 'Reducer Version Change Detected', important: true }); } // Purge store persistStore(store, config, startApp).purge(); AsyncStorage.setItem('reducerVersion', reducerVersion); } else { persistStore(store, config, startApp); } }).catch(() => { persistStore(store, config, startApp); AsyncStorage.setItem('reducerVersion', reducerVersion); })
會在AsyncStorage存儲一個reducer版本號,這個是在應用配置文件中能夠配置,首次執行持久化時存儲該版本號及store,若reducer版本號變動則清空原來存儲的store,不然傳入store給持久化方法persistStore
便可。
persistStore(store, [config, callback])
該方法主要實現store的持久化以及分發rehydration action :
接收參數主要以下:
和persisStore同樣,依然是在建立redux store時初始化註冊rehydrate拓展:
// add the autoRehydrate enhancer if (ReduxPersist.active) { enhancers.push(autoRehydrate()); }
該方法實現的功能很簡單,即便用 持久化的數據恢復(rehydrate) store 中數據,它實際上是註冊了一個autoRehydarte reducer,會接收前文persistStore方法分發的rehydrate action,而後合併state。
固然,autoRehydrate不是必須的,咱們能夠自定義恢復store方式:
import {REHYDRATE} from 'redux-persist/constants'; //... case REHYDRATE: const incoming = action.payload.reducer if (incoming) { return { ...state, ...incoming } } return state;
須要注意的是redux-persist庫已經發布到v5.x,而本文介紹的以v4.x爲準,新版本有一些更新,詳細請點擊查看。
前面已經提到Redux與Immutable的整合,上文使用的redux-persist默認也只能處理原生JavaScript對象的redux store state,因此須要拓展以兼容Immutable。
使用redux-persist-immutable庫能夠很容易實現兼容,所作的僅僅是使用其提供的persistStore
方法替換redux-persist所提供的方法:
import { persistStore } from 'redux-persist-immutable';
咱們知道持久化store時,針對的最好是原生JavaScript對象,由於一般Immutable結構數據有不少輔助信息,不易於存儲,因此須要定義持久化及恢復數據時的轉換操做:
import R from 'ramda'; import Immutable, { Iterable } from 'immutable'; // change this Immutable object into a JS object const convertToJs = (state) => state.toJS(); // optionally convert this object into a JS object if it is Immutable const fromImmutable = R.when(Iterable.isIterable, convertToJs); // convert this JS object into an Immutable object const toImmutable = (raw) => Immutable.fromJS(raw); // the transform interface that redux-persist is expecting export default { out: (state) => { return toImmutable(state); }, in: (raw) => { return fromImmutable(raw); } };
如上,輸出對象中的in和out分別對應持久化及恢復數據時的轉換操做,實現的只是使用fromJS()
和toJS()
轉換Js和Immutable數據結構,使用方式以下:
import immutablePersistenceTransform from '../services/ImmutablePersistenceTransform' persistStore(store, { transforms: [immutablePersistenceTransform] }, startApp);
在項目中引入Immutable之後,須要儘可能保證如下幾點:
前面兩點已經在前面兩節闡述過,第三點過於Navigation兼容Immutable,其實就是使Navigation路由狀態兼容Immutable,在App應用導航與路由一節已經介紹如何將Navigation路由狀態鏈接至Redux store,若是應用使用了Immutable庫,則須要另外處理,將Navigation router state轉換爲Immutable,修改前面提到的NavigationReducer:
const initialState = Immutable.fromJS({ index: 0, routes: [{ routeName: routes.Login.name, key: routes.Login.name }] }); const NavigationReducer = (state = initialState, action) => { const newState = state.merge(AppNavigation.router.getStateForAction(action, state.toJS())); return newState || state; };
將默認初始狀態轉換爲Immutable,而且合併state時使用merge()
方法。
最後要介紹的模塊是異步任務管理,在應用開發過程當中,最主要的異步任務就是數據HTTP請求,因此咱們講異步任務管理,主要關注在數據HTTP請求的流程管理。
本項目中使用axios做爲HTTP請求庫,axios是一個Promise格式的HTTP客戶端,選擇此庫的緣由主要有如下幾點:
redux-saga是一個致力於使應用中如數據獲取,本地緩存訪問等異步任務易於管理,高效運行,便於測試,能更好的處理異常的三方庫。
Redux-saga是一個redux中間件,它就像應用中一個單獨的進程,只負責管理異步任務,它能夠接受應用主進程的redux action以決定啓動,暫停或者是取消進程任務,它也能夠訪問redux應用store state,而後分發action。
redux-saga是一箇中間件,因此首先調用createSagaMiddleware
方法建立中間件,而後使用redux的applyMiddleware
方法啓用中間件,以後使用compose輔助方法傳給createStore
建立store,最後調用run
方法啓動根saga:
import { createStore, applyMiddleware, compose } from 'redux'; import createSagaMiddleware from 'redux-saga'; import rootSaga from '../sagas/' const sagaMiddleware = createSagaMiddleware({ sagaMonitor }); middleware.push(sagaMiddleware); enhancers.push(applyMiddleware(...middleware)); const store = createStore(rootReducer, initialState, compose(...enhancers)); // kick off root saga sagaMiddleware.run(rootSaga);
在項目中一般會有不少並列模塊,每一個模塊的saga流也應該是並列的,須要以多分支形式並列,redux-saga提供的fork
方法就是以新開分支的形式啓動當前saga流:
import { fork, takeEvery } from 'redux-saga/effects'; import LoginSagas from './LoginSagas'; const sagas = [ ...LoginSagas, ...StartAppSagas ]; export default function * root() { yield sagas.map(saga => fork(saga)); };
如上,首先收集全部模塊根saga,而後遍歷數組,啓動每個saga流根saga。
以LoginSagas爲例,對於登陸這一操做,可能在用戶開始登陸,登陸成功後須要進行一些異步請求,因此列出loginSaga, loginSuccessSaga,另外用戶退出帳戶時也可能須要進行HTTP請求,因此將logoutSaga放在此處:
... // process login actions export function * loginSaga () { yield takeLatest(LoginTypes.LOGIN, login); } export function * loginSuccessSaga () { yield takeLatest(LoginTypes.LOGIN_SUCCESS, loginSuccess); } export function * logoutSaga () { yield takeLatest(LogoutTypes.LOGOUT, logout); } const sagas = [ loginSaga, loginSuccessSaga, logoutSaga ]; export default sagas;
在loginSaga內使用takeLatest
方法監聽LoginTypes.LOGIN
action,當接收到該action時,調用login
,login本質上仍是一個saga,在裏面處理異步任務:
function * login (action) { const { username, password } = action.payload || {}; if (username && password) { const res = yield requestLogin({ username, password }); const { data } = res || {}; if (data && data.success) { yield put(LoginActions.loginSuccess({ username, password, isLogin: true })); } else { yield put(LoginActions.loginFail({ username, password, isLogin: false })); } } else { yield put(LoginActions.loginFail({ username, password, isLogin: false })); } }
requestLogin
方法就是一個登陸HTTP請求,用戶名和密碼參數從LoginTypes.LOGIN
action傳遞的負載取得,yield
語句取回請求響應,賦值給res,隨後經過響應內容判斷登陸是否成功:
LoginActions.loginSuccess
action,隨後將執行監聽此action的reducer及loginSuccessSaga
saga;LoginActions.loginFail
action;put是redux-saga提供的可分發action方法。
前面已經配置好可使用Reactotron捕獲應用全部redux和action,而redux-saga是一類redux中間件,因此捕獲sagas須要額外配置,建立store時,在saga中間件內添加sagaMonitor服務,監聽saga:
const sagaMonitor = Config.useReactotron ? console.tron.createSagaMonitor() : null; const sagaMiddleware = createSagaMiddleware({ sagaMonitor }); middleware.push(sagaMiddleware); ...
本文較詳細的總結了我的從0到1搭建一個項目架構的過程,對React,React Native, Redux應用和項目工程實踐都有了更深的理解及思考,在大前端成長之路繼續砥礪前行。