React Native+react-navigation+redux開發必備指南

React Native+Redux開發實用教程

爲了幫助你們快速上手在React Native與Redux開發,在這本文中將向你們介紹如何在React Native中使用Redux和react-navigation組合?,以及一些必備基礎以及高級知識html

本參考了《新版React Native+Redux打造高質量上線App》課程的部分講解,更多關於React Native與Redux的實戰技巧可在《新版React Native+Redux打造高質量上線App》中查看。react

那麼如何在React Native中使用Redux和react-navigation組合?呢?android

在使用 React Navigation 的項目中,想要集成 redux 就必需要引入 react-navigation-redux-helpers 這個庫。git

第一步:安裝react-navigation-redux-helpers

npm install --save react-navigation-redux-helpers
複製代碼

第二步:配置Navigation

import React from 'react';
import {createStackNavigator, createSwitchNavigator} from 'react-navigation';
import {connect} from 'react-redux';
import {createReactNavigationReduxMiddleware, reduxifyNavigator} from 'react-navigation-redux-helpers';

export const rootCom = 'Init';//設置根路由

export const RootNavigator = createSwitchNavigator({
   ...
});

/** * 1.初始化react-navigation與redux的中間件, * 該方法的一個很大的做用就是爲reduxifyNavigator的key設置actionSubscribers(行爲訂閱者) * 設置訂閱者@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L29 * 檢測訂閱者是否存在@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L97 * @type {Middleware} */
export const middleware = createReactNavigationReduxMiddleware(
    'root',
    state => state.nav
);
 
/** * 2.將根導航器組件傳遞給 reduxifyNavigator 函數, * 並返回一個將navigation state 和 dispatch 函數做爲 props的新組件; * 注意:要在createReactNavigationReduxMiddleware以後執行 */
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');

/** * State到Props的映射關係 * @param state */
const mapStateToProps = state => ({
    state: state.nav,//v2
});
/** * 3.鏈接 React 組件與 Redux store */
export default connect(mapStateToProps)(AppWithNavigationState);
複製代碼

提示:createReactNavigationReduxMiddleware方法要放到reduxifyNavigator以前執行不然會報錯,以上代碼片斷的完整部分能夠在課程源碼中查找。github

第二步:配置Reducer

import {combineReducers} from 'redux'
import theme from './theme'
import {rootCom, RootNavigator} from '../navigator/AppNavigators';

//1.指定默認state
const navState = RootNavigator.router.getStateForAction(RootNavigator.router.getActionForPathAndParams(rootCom));

/** * 2.建立本身的 navigation reducer, */
const navReducer = (state = navState, action) => {
    const nextState = RootNavigator.router.getStateForAction(action, state);
    // 若是`nextState`爲null或未定義,只需返回原始`state`
    return nextState || state;
};

/** * 3.合併reducer * @type {Reducer<any> | Reducer<any, AnyAction>} */
const index = combineReducers({
    nav: navReducer,
    theme: theme,
});

export default index;
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。npm

第三步:配置store

import {applyMiddleware, createStore} from 'redux'
import thunk from 'redux-thunk'
import reducers from '../reducer'
import {middleware} from '../navigator/AppNavigators'

const middlewares = [
    middleware,
];
/** * 建立store */
export default createStore(reducers, applyMiddleware(...middlewares));
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。redux

第四步:在組件中應用

import React, {Component} from 'react';
import {Provider} from 'react-redux';
import AppNavigator from './navigator/AppNavigators';
import store from './store'

type Props = {};
export default class App extends Component<Props> {
    render() {
        /** * 將store傳遞給App框架 */
        return <Provider store={store}> <AppNavigator/> </Provider>
    }
}
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。react-native

通過上述4步呢,咱們已經完成了react-navigaton+redux的集成,那麼如何使用它呢?bash

使用react-navigaton+redux

1.訂閱state服務器

import React from 'react';
import {connect} from 'react-redux';

class TabBarComponent extends React.Component {
    render() {
        return (
            <BottomTabBar {...this.props} activeTintColor={this.props.theme} /> ); } } const mapStateToProps = state => ({ theme: state.theme.theme, }); export default connect(mapStateToProps)(TabBarComponent); 複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

在上述代碼中咱們訂閱了store中的theme state,而後該組件就能夠經過this.props.theme獲取到所訂閱的theme state了。

2.觸發action改變state

import React, {Component} from 'react';
import {connect} from 'react-redux'
import {onThemeChange} from '../action/theme'
import {StyleSheet, Text, View, Button} from 'react-native';

type Props = {};
class FavoritePage extends Component<Props> {
    render() {
        return (
            <View style={styles.container}> <Text style={styles.welcome}>FavoritePage</Text> <Button title="改變主題色" onPress={() => { // let {dispatch} = this.props.navigation; // dispatch(onThemeChange('red')) this.props.onThemeChange('#096'); }} /> </View> ); } } const mapStateToProps = state => ({}); const mapDispatchToProps = dispatch => ({ onThemeChange: (theme) => dispatch(onThemeChange(theme)), }); export default connect(mapStateToProps, mapDispatchToProps)(FavoritePage); ... 複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

觸發action有兩種方式:

  • 一種是經過mapDispatchToProps將dispatch建立函數和props綁定,這樣就能夠經過this.props.onThemeChange('#096')調用這個dispatch建立函數來觸發onThemeChange action了;

  • 另一種方式是經過this.props中的navigation來獲取dispatch,而後經過這個dispatch手動觸發一個action:

    let {dispatch} = this.props.navigation;
    dispatch(onThemeChange('red'))
    複製代碼

    以上代碼片斷的完整部分能夠在課程源碼中查找。

在Redux+react-navigation場景中處理 Android 中的物理返回鍵

在Redux+react-navigation場景中處理Android的物理返回鍵須要注意當前路由的因此位置,而後根據指定路由的索引位置來進行操做,這裏須要用到BackHandler

import React, {Component} from 'react';
import {BackHandler} from "react-native";
import {NavigationActions} from "react-navigation";
import {connect} from 'react-redux';
import DynamicTabNavigator from '../navigator/DynamicTabNavigator';
import NavigatorUtil from '../navigator/NavigatorUtil';

type Props = {};

class HomePage extends Component<Props> {
    componentDidMount() {
        BackHandler.addEventListener("hardwareBackPress", this.onBackPress);
    }

    componentWillUnmount() {
        BackHandler.removeEventListener("hardwareBackPress", this.onBackPress);
    }

    /** * 處理 Android 中的物理返回鍵 * https://reactnavigation.org/docs/en/redux-integration.html#handling-the-hardware-back-button-in-android * @returns {boolean} */
    onBackPress = () => {
        const {dispatch, nav} = this.props;
        //if (nav.index === 0) {
        if (nav.routes[1].index === 0) {//若是RootNavigator中的MainNavigator的index爲0,則不處理返回事件
            return false;
        }
        dispatch(NavigationActions.back());
        return true;
    };

    render() {
        return <DynamicTabNavigator/>;
    }
}

const mapStateToProps = state => ({
    nav: state.nav,
});

export default connect(mapStateToProps)(HomePage);
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

2end

API

combineReducers(reducers)

隨着應用變得愈來愈複雜,能夠考慮將 reducer 函數 拆分紅多個單獨的函數,拆分後的每一個函數負責獨立管理 state 的一部分。

函數原型:combineReducers(reducers)

  • 參數:reducers (Object): 一個對象,它的值(value)對應不一樣的 reducer 函數,這些 reducer 函數後面會被合併成一個。下面會介紹傳入 reducer 函數須要知足的規則。

  • 每一個傳入 combineReducers 的 reducer 都需知足如下規則:

    • 全部未匹配到的 action,必須把它接收到的第一個參數也就是那個 state 原封不動返回。
    • 永遠不能返回 undefined。當過早 return 時很是容易犯這個錯誤,爲了不錯誤擴散,遇到這種狀況時 combineReducers 會拋異常。
    • 若是傳入的 state 就是 undefined,必定要返回對應 reducer 的初始 state。根據上一條規則,初始 state 禁止使用 undefined。使用 ES6 的默認參數值語法來設置初始 state 很容易,但你也能夠手動檢查第一個參數是否爲 undefined。

返回值

(Function):一個調用 reducers 對象裏全部 reducer 的 reducer,而且構造一個與 reducers 對象結構相同的 state 對象。

combineReducers 輔助函數的做用是,把一個由多個不一樣 reducer 函數做爲 value 的 object,合併成一個最終的 reducer 函數,而後就能夠對這個 reducer 調用 createStore 方法。

合併後的 reducer 能夠調用各個子 reducer,並把它們返回的結果合併成一個 state 對象。 由 combineReducers() 返回的 state 對象,會將傳入的每一個 reducer 返回的 state 按其傳遞給 combineReducers() 時對應的 key 進行命名。

提示:在 reducer 層級的任何一級均可以調用 combineReducers。並非必定要在最外層。實際上,你能夠把一些複雜的子 reducer 拆分紅單獨的孫子級 reducer,甚至更多層。

createStore

函數原型:createStore(reducer, [preloadedState], enhancer)

參數

  • reducer (Function)::項目的根reducer。
  • [preloadedState] (any):這個參數是可選的, 用於設置 state 初始狀態。這對開發同構應用時很是有用,服務器端 redux 應用的 state 結構能夠與客戶端保持一致, 那麼客戶端能夠將從網絡接收到的服務端 state 直接用於本地數據初始化。
  • enhancer (Function): Store enhancer 是一個組合 store creator 的高階函數,返回一個新的強化過的 store creator。這與 middleware 類似,它也容許你經過複合函數改變 store 接口。

返回值

  • (Store): 保存了應用全部 state 的對象。改變 state 的唯一方法是 dispatch action。你也能夠 subscribe 監聽 state 的變化,而後更新 UI。

示例

import { createStore } from 'redux'

function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text])
    default:
      return state
  }
}

let store = createStore(todos, ['Use Redux'])

store.dispatch({
  type: 'ADD_TODO',
  text: 'Read the docs'
})

console.log(store.getState())
// [ 'Use Redux', 'Read the docs' ]
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

注意事項

  • 應用中不要建立多個 store!相反,使用 combineReducers 來把多個 reducer 建立成一個根 reducer。
  • 你能夠決定 state 的格式。你可使用普通對象或者 Immutable 這類的實現。若是你不知道如何作,剛開始可使用普通對象。
  • 若是 state 是普通對象,永遠不要修改它!好比,reducer 裏不要使用 Object.assign(state, newData),應該使用 Object.assign({}, state, newData)。這樣纔不會覆蓋舊的 state。若是能夠的話,也可使用 對象拓展操做符(object spread spread operator 特性中的 return { ...state, ...newData }。
  • 對於服務端運行的同構應用,爲每個請求建立一個 store 實例,以此讓 store 相隔離。dispatch 一系列請求數據的 action 到 store 實例上,等待請求完成後再在服務端渲染應用。
  • 當 store 建立後,Redux 會 dispatch 一個 action 到 reducer 上,來用初始的 state 來填充 store。你不須要處理這個 action。但要記住,若是第一個參數也就是傳入的 state 是 undefined 的話,reducer 應該返回初始的 state 值。
  • 要使用多個 store 加強器的時候,你可能須要使用 compose

applyMiddleware

函數原型:applyMiddleware(...middleware)

使用包含自定義功能的 middleware 來擴展 Redux。

技巧

  • react-navigation+redux;
  • 如何防止重複建立實例:
    • 方式一:單例+Map+工廠;
    • 方式二:頁面保存實例變量,傳遞給,Action使用;
    • 方式三:在action中建立實例;
  • 如何動態的設置store,和動態獲取store(難點:storekey不固定);
  • 如何實現可取消的redux action:可參考SearchPage的設計;

上述的實戰技巧可在新版React Native+Redux打造高質量上線App中獲取;

問答

  • Redux是如何實現JS的可預測狀態的管理?
    • 單一數據源;
    • 全部數據都是隻讀的,要想修改數據,必須 dispatch 一個 action 來描述什麼發生了改變;
    • 當處理 action 時,必須生成一個新的 state,不得直接修改原始對象;
  • Redux的核心思想是什麼?
    • 單向數據流的是Redux架構的設計核心;

如何作到從不直接修改 state ?

從不直接修改 state 是 Redux 的核心理念之一:爲實現這一理念,能夠經過一下兩種方式:

[1.經過Object.assign()建立對象拷貝, 而拷貝中會包含新建立或更新過的屬性值](coding.imooc.com/class/304.h…)

在下面的 todoApp 示例中, Object.assign() 將會返回一個新的 state 對象, 而其中的 visibilityFilter 屬性被更新了:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

[2. 經過經過ES7的新特性對象展開運算符(Object Spread Operator)](coding.imooc.com/class/304.h…)

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return { ...state, visibilityFilter: action.filter }
    default:
      return state
  }
}
複製代碼

以上代碼片斷的完整部分能夠在課程源碼中查找。

這樣你就能輕鬆的跳回到這個對象以前的某個狀態(想象一個撤銷功能)。

總結

  • Redux 應用只有一個單一的 store。當須要拆分數據處理邏輯時,你應該使用 reducer 組合 而不是建立多個 store;
  • redux一個特色是:狀態共享,全部的狀態都放在一個store中,任何component均可以訂閱store中的數據;
  • 並非全部的state都適合放在store中,這樣會讓store變得很是龐大,如某個狀態只被一個組件使用,不存在狀態共享,能夠不放在store中;

未完待續

參考

相關文章
相關標籤/搜索