為了幫助大家快速上手在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 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中德谅;