React Native+react-navigation+redux開發(fā)教程

React Native+Redux開發(fā)實(shí)用教程

為了幫助大家快速上手在React Native與Redux開發(fā)媒吗,在這本文中將向大家介紹如何在React Native中使用Redux和react-navigation組合?砚作,以及一些必備基礎(chǔ)以及高級(jí)知識(shí)蒲凶。

本參考了《新版React Native+Redux打造高質(zhì)量上線App》課程的部分講解,更多關(guān)于React Native與Redux的實(shí)戰(zhàn)技巧可在《新版React Native+Redux打造高質(zhì)量上線App》中查看帝雇。

那么如何在React Native中使用Redux和react-navigation組合?呢绪爸?

在使用 React Navigation 的項(xiàng)目中,想要集成 redux 就必須要引入 react-navigation-redux-helpers 這個(gè)庫厕宗。

第一步:安裝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';//設(shè)置根路由

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

/**
 * 1.初始化react-navigation與redux的中間件画舌,
 * 該方法的一個(gè)很大的作用就是為reduxifyNavigator的key設(shè)置actionSubscribers(行為訂閱者)
 * 設(shè)置訂閱者@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L29
 * 檢測(cè)訂閱者是否存在@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.將根導(dǎo)航器組件傳遞給 reduxifyNavigator 函數(shù),
 * 并返回一個(gè)將navigation state 和 dispatch 函數(shù)作為 props的新組件堕担;
 * 注意:要在createReactNavigationReduxMiddleware之后執(zhí)行
 */
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');

/**
 * State到Props的映射關(guān)系
 * @param state
 */
const mapStateToProps = state => ({
    state: state.nav,//v2
});
/**
 * 3.連接 React 組件與 Redux store
 */
export default connect(mapStateToProps)(AppWithNavigationState);

提示:createReactNavigationReduxMiddleware方法要放到reduxifyNavigator之前執(zhí)行否則會(huì)報(bào)錯(cuò),以上代碼片段的完整部分可以在課程源碼中查找曲聂。

第二步:配置Reducer

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

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

/**
 * 2.創(chuàng)建自己的 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;

以上代碼片段的完整部分可以在課程源碼中查找朋腋。

第三步:配置store

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

const middlewares = [
    middleware,
];
/**
 * 創(chuàng)建store
 */
export default createStore(reducers, applyMiddleware(...middlewares));

以上代碼片段的完整部分可以在課程源碼中查找厕鹃。

第四步:在組件中應(yīng)用

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>
    }
}

以上代碼片段的完整部分可以在課程源碼中查找。

經(jīng)過上述4步呢乍丈,我們已經(jīng)完成了react-navigaton+redux的集成剂碴,那么如何使用它呢?

使用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.觸發(fā)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);
...

以上代碼片段的完整部分可以在課程源碼中查找请垛。

觸發(fā)action有兩種方式:

  • 一種是通過mapDispatchToProps將dispatch創(chuàng)建函數(shù)和props綁定催训,這樣就可以通過this.props.onThemeChange('#096')調(diào)用這個(gè)dispatch創(chuàng)建函數(shù)來觸發(fā)onThemeChange action了;

  • 另外一種方式是通過this.props中的navigation來獲取dispatch宗收,然后通過這個(gè)dispatch手動(dòng)觸發(fā)一個(gè)action:

    let {dispatch} = this.props.navigation;
    dispatch(onThemeChange('red'))
    

    以上代碼片段的完整部分可以在課程源碼中查找漫拭。

在Redux+react-navigation場(chǎng)景中處理 Android 中的物理返回鍵

在Redux+react-navigation場(chǎng)景中處理Android的物理返回鍵需要注意當(dāng)前路由的所以位置,然后根據(jù)指定路由的索引位置來進(jìn)行操作混稽,這里需要用到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)

隨著應(yīng)用變得越來越復(fù)雜礼旅,可以考慮將 reducer 函數(shù) 拆分成多個(gè)單獨(dú)的函數(shù),拆分后的每個(gè)函數(shù)負(fù)責(zé)獨(dú)立管理 state 的一部分洽洁。

函數(shù)原型:combineReducers(reducers)

  • 參數(shù):reducers (Object): 一個(gè)對(duì)象痘系,它的值(value)對(duì)應(yīng)不同的 reducer 函數(shù),這些 reducer 函數(shù)后面會(huì)被合并成一個(gè)饿自。下面會(huì)介紹傳入 reducer 函數(shù)需要滿足的規(guī)則汰翠。

  • 每個(gè)傳入 combineReducers 的 reducer 都需滿足以下規(guī)則:

    • 所有未匹配到的 action,必須把它接收到的第一個(gè)參數(shù)也就是那個(gè) state 原封不動(dòng)返回昭雌。
    • 永遠(yuǎn)不能返回 undefined复唤。當(dāng)過早 return 時(shí)非常容易犯這個(gè)錯(cuò)誤,為了避免錯(cuò)誤擴(kuò)散城豁,遇到這種情況時(shí) combineReducers 會(huì)拋異常苟穆。
    • 如果傳入的 state 就是 undefined抄课,一定要返回對(duì)應(yīng) reducer 的初始 state唱星。根據(jù)上一條規(guī)則雳旅,初始 state 禁止使用 undefined。使用 ES6 的默認(rèn)參數(shù)值語法來設(shè)置初始 state 很容易间聊,但你也可以手動(dòng)檢查第一個(gè)參數(shù)是否為 undefined攒盈。

返回值

(Function):一個(gè)調(diào)用 reducers 對(duì)象里所有 reducer 的 reducer,并且構(gòu)造一個(gè)與 reducers 對(duì)象結(jié)構(gòu)相同的 state 對(duì)象哎榴。

combineReducers 輔助函數(shù)的作用是型豁,把一個(gè)由多個(gè)不同 reducer 函數(shù)作為 value 的 object,合并成一個(gè)最終的 reducer 函數(shù)尚蝌,然后就可以對(duì)這個(gè) reducer 調(diào)用 createStore 方法迎变。

合并后的 reducer 可以調(diào)用各個(gè)子 reducer,并把它們返回的結(jié)果合并成一個(gè) state 對(duì)象飘言。 由 combineReducers() 返回的 state 對(duì)象衣形,會(huì)將傳入的每個(gè) reducer 返回的 state 按其傳遞給 combineReducers() 時(shí)對(duì)應(yīng)的 key 進(jìn)行命名。

提示:在 reducer 層級(jí)的任何一級(jí)都可以調(diào)用 combineReducers姿鸿。并不是一定要在最外層谆吴。實(shí)際上,你可以把一些復(fù)雜的子 reducer 拆分成單獨(dú)的孫子級(jí) reducer苛预,甚至更多層句狼。

createStore

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

參數(shù)

  • reducer (Function)::項(xiàng)目的根reducer。
  • [preloadedState] (any):這個(gè)參數(shù)是可選的, 用于設(shè)置 state 初始狀態(tài)热某。這對(duì)開發(fā)同構(gòu)應(yīng)用時(shí)非常有用腻菇,服務(wù)器端 redux 應(yīng)用的 state 結(jié)構(gòu)可以與客戶端保持一致, 那么客戶端可以將從網(wǎng)絡(luò)接收到的服務(wù)端 state 直接用于本地?cái)?shù)據(jù)初始化。
  • enhancer (Function): Store enhancer 是一個(gè)組合 store creator 的高階函數(shù)昔馋,返回一個(gè)新的強(qiáng)化過的 store creator芜繁。這與 middleware 相似,它也允許你通過復(fù)合函數(shù)改變 store 接口绒极。

返回值

  • (Store): 保存了應(yīng)用所有 state 的對(duì)象骏令。改變 state 的惟一方法是 dispatch action。你也可以 subscribe 監(jiān)聽 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' ]

以上代碼片段的完整部分可以在課程源碼中查找。

注意事項(xiàng)

  • 應(yīng)用中不要?jiǎng)?chuàng)建多個(gè) store铡俐!相反凰兑,使用 combineReducers 來把多個(gè) reducer 創(chuàng)建成一個(gè)根 reducer。
  • 你可以決定 state 的格式审丘。你可以使用普通對(duì)象或者 Immutable 這類的實(shí)現(xiàn)吏够。如果你不知道如何做,剛開始可以使用普通對(duì)象。
  • 如果 state 是普通對(duì)象锅知,永遠(yuǎn)不要修改它播急!比如,reducer 里不要使用 Object.assign(state, newData)售睹,應(yīng)該使用 Object.assign({}, state, newData)桩警。這樣才不會(huì)覆蓋舊的 state。如果可以的話昌妹,也可以使用 對(duì)象拓展操作符(object spread spread operator 特性中的 return { ...state, ...newData }捶枢。
  • 對(duì)于服務(wù)端運(yùn)行的同構(gòu)應(yīng)用,為每一個(gè)請(qǐng)求創(chuàng)建一個(gè) store 實(shí)例飞崖,以此讓 store 相隔離烂叔。dispatch 一系列請(qǐng)求數(shù)據(jù)的 action 到 store 實(shí)例上,等待請(qǐng)求完成后再在服務(wù)端渲染應(yīng)用固歪。
  • 當(dāng) store 創(chuàng)建后长已,Redux 會(huì) dispatch 一個(gè) action 到 reducer 上,來用初始的 state 來填充 store昼牛。你不需要處理這個(gè) action术瓮。但要記住,如果第一個(gè)參數(shù)也就是傳入的 state 是 undefined 的話贰健,reducer 應(yīng)該返回初始的 state 值胞四。
  • 要使用多個(gè) store 增強(qiáng)器的時(shí)候,你可能需要使用 compose

applyMiddleware

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

使用包含自定義功能的 middleware 來擴(kuò)展 Redux伶椿。

技巧

  • react-navigation+redux辜伟;
  • 如何防止重復(fù)創(chuàng)建實(shí)例:
    • 方式一:?jiǎn)卫?Map+工廠;
    • 方式二:頁面保存實(shí)例變量脊另,傳遞給导狡,Action使用;
    • 方式三:在action中創(chuàng)建實(shí)例偎痛;
  • 如何動(dòng)態(tài)的設(shè)置store旱捧,和動(dòng)態(tài)獲取store(難點(diǎn):storekey不固定);
  • 如何實(shí)現(xiàn)可取消的redux action:可參考SearchPage的設(shè)計(jì)踩麦;

上述的實(shí)戰(zhàn)技巧可在新版React Native+Redux打造高質(zhì)量上線App中獲让渡摹;

問答

  • Redux是如何實(shí)現(xiàn)JS的可預(yù)測(cè)狀態(tài)的管理谓谦?
    • 單一數(shù)據(jù)源贫橙;
    • 所有數(shù)據(jù)都是只讀的,要想修改數(shù)據(jù)反粥,必須 dispatch 一個(gè) action 來描述什么發(fā)生了改變卢肃;
    • 當(dāng)處理 action 時(shí)疲迂,必須生成一個(gè)新的 state,不得直接修改原始對(duì)象莫湘;
  • Redux的核心思想是什么尤蒿?
    • 單向數(shù)據(jù)流的是Redux架構(gòu)的設(shè)計(jì)核心;

如何做到從不直接修改 state 逊脯?

從不直接修改 state 是 Redux 的核心理念之一:為實(shí)現(xiàn)這一理念优质,可以通過一下兩種方式:

1.通過Object.assign()創(chuàng)建對(duì)象拷貝, 而拷貝中會(huì)包含新創(chuàng)建或更新過的屬性值

在下面的 todoApp 示例中, Object.assign() 將會(huì)返回一個(gè)新的 state 對(duì)象, 而其中的 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的新特性對(duì)象展開運(yùn)算符(Object Spread Operator)

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

以上代碼片段的完整部分可以在課程源碼中查找军洼。

這樣你就能輕松的跳回到這個(gè)對(duì)象之前的某個(gè)狀態(tài)(想象一個(gè)撤銷功能)。

總結(jié)

  • Redux 應(yīng)用只有一個(gè)單一的 store演怎。當(dāng)需要拆分?jǐn)?shù)據(jù)處理邏輯時(shí)匕争,你應(yīng)該使用 reducer 組合 而不是創(chuàng)建多個(gè) store;
  • redux一個(gè)特點(diǎn)是:狀態(tài)共享爷耀,所有的狀態(tài)都放在一個(gè)store中甘桑,任何component都可以訂閱store中的數(shù)據(jù);
  • 并不是所有的state都適合放在store中歹叮,這樣會(huì)讓store變得非常龐大跑杭,如某個(gè)狀態(tài)只被一個(gè)組件使用,不存在狀態(tài)共享咆耿,可以不放在store中德谅;

未完待續(xù)

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萨螺,隨后出現(xiàn)的幾起案子窄做,更是在濱河造成了極大的恐慌,老刑警劉巖慰技,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椭盏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吻商,警方通過查閱死者的電腦和手機(jī)掏颊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艾帐,“玉大人蚯舱,你說我怎么就攤上這事⊙诟颍” “怎么了枉昏?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)揍鸟。 經(jīng)常有香客問我兄裂,道長(zhǎng)句旱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任晰奖,我火速辦了婚禮谈撒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匾南。我一直安慰自己啃匿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布蛆楞。 她就那樣靜靜地躺著溯乒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豹爹。 梳的紋絲不亂的頭發(fā)上裆悄,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音臂聋,去河邊找鬼光稼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛孩等,可吹牛的內(nèi)容都是我干的艾君。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肄方,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冰垄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扒秸,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤播演,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后伴奥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體写烤,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年拾徙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洲炊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尼啡,死狀恐怖暂衡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情崖瞭,我是刑警寧澤狂巢,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站书聚,受9級(jí)特大地震影響唧领,放射性物質(zhì)發(fā)生泄漏藻雌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一斩个、第九天 我趴在偏房一處隱蔽的房頂上張望胯杭。 院中可真熱鬧,春花似錦受啥、人聲如沸做个。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽居暖。三九已至,卻和暖如春核畴,著一層夾襖步出監(jiān)牢的瞬間膝但,已是汗流浹背冲九。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工谤草, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莺奸。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓丑孩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親灭贷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子温学,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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