前言
- 本文
有配套視頻,可以酌情觀看。 - 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我討論。
- 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關,如文中內容對您造成不便,煩請聯系 277511806@qq.com 處理,謝謝。
- 轉載麻煩注明出處,謝謝。
- 本篇資源:鏈接: https://pan.baidu.com/s/1cGjEua 密碼: scea
redux簡介
-
簡單來說,
redux就是幫我們統一管理了react組件的state狀態。 -
為什么要使用
redux統一管理state呢?沒有redux我們依舊可以開發 APP,但是當APP的復雜度到達一定程度的時候,擺在我們面前的就是難以維護的代碼(其中包含組件大量的異步回調,數據處理等等),但是使用redux也會增加我們整個項目的復雜度,這就需要我們在兩者之間進行權衡了,對於這一部分,redux開發者給我們下面幾個參考點:-
以下幾種情況不需要使用
redux:-
整體 UI 很簡單,沒有太多交互。
-
不需要與服務器進行大量交互,也沒有使用 WebSocket。
-
視圖層只從單一來源獲取數據。
-
-
以下幾種情況可考慮使用
redux:-
用戶的交互復雜。
-
根據層級用戶划分功能。
-
多個用戶之間協作。
-
與服務器大量交互,或使用了 WebSocket。
-
視圖層需要從多個來源獲取數據。
-
遇到 React 無法解決的問題。
-
-
總結以上內容:
redux適用於 多交互,多數據源,復雜程度高的工程中。
-
-
也就是說,當我們的組件出現
某個狀態需要共享,需要改變另一個組件狀態等傳值比較不容易的情況。就可以考慮redux,當然還有其他redux的替代產品供我們使用。
譯注:
WebSocket:被稱為下一代客戶端與服務端的異步通信方法。取代了單個的TCP套接字,使用ws或wss協議,可用於任意的客戶端和服務器程序。WebSocket目前由W3C進行標准化。主要的優點是服務器和客戶端可以彼此相互推送信息,允許跨域通信。
redux 必要知識
- 使用
redux之前,基本的東西還是要都懂的,數據流向介紹:

-
Action:行為。它的作用就是將我們更新組件的
狀態(state)的每個動作抽象為一個行為,它有一個必須的參數type,定義了Action(行為)的名稱,其他參數可自定義。寫法:{ type: 'TEST_ACTION', key1: 'value', ... keyN: value } -
因為
Action是個對象,所以,我們需要創建這個對象,那創建這個對象的方法叫做ActionCreator,寫法:function testAction(key1: ?string, ..., keyN: ?string) { return { type: "TEST_ACTION", key1: key1, ... keyN: keyN } } -
Reducer:reducer 的作用就是根據傳入的 Action行為和舊的 state對象,返回一個新的 state ,然后組件會根據 state 刷新。當我們確定了組件的 state 對象結構 和 action 行為的時候就可以編寫 reducer 中的內容。寫法:
function testReducer(state, action) { let key1 = action.key1; switch(action.type) { case TEST_ACTION: return { ...state, key1: key1 + '變化' }; default: return state; } }; export default testReducer; -
當然我們的工程中可能會有多個 reducer 的情況,通過 combineReducers 可以將多個 reducer 合成統一管理。
import { combineReducers } from 'redux'; import testReducer1 from './testReducer1'; import testReducer2 from './testReducer2'; export default = combineReducers({ testReducer1, testReducer2 }); -
reducer 是一個純函數(同樣的輸入,必須有同樣的輸出,需要遵循 3 個約束):
-
不可修改傳入的參數。
-
一定要干凈,沒有API請求,沒有變量修改,單純執行計算,沒有特殊情況。
-
調用非純函數(Date.now()、Math.random()等),每次都會得到不同結果導致數據錯誤等安全問題。
-
當傳入的 state 與 舊state 相比沒有區別,返回的 新state也應該一摸一樣。
-
-
Store:當 reducer 返回了新的 state 后,這個 state 怎么傳到組件和存儲就成了問題,redux 就是把這個狀態統一放到 store 中進行管理。
import { createStore } from 'redux'; const store = createStore(reducers); -
上面的代碼根據 reducers 創建了一個 store方法集(它並不是一個對象),然后再 store 中提供一些方法供我們使用:
// 獲取當前 state store.getState() // 發送action,根據我們前面 注冊的reducers 處理state store.dispath(action) // 替換當前 state 中的 reducer store.replaceReducer(nextReducer) // 添加監聽 store.subscribe(listener) -
另外
redux有 5個 全局方法:-
createStore:創建一個readux store 來存儲應用中所有的state,應用中只能存在一個 storecreateStore(reducer, [initialState],enhancer); -
combineReducers:把多個reducer函數作為value的object,合並成一個reducers函數,然后就可以通過reducers調用各個子reducer,state 對象的結構由傳入的多個 reducer 的 key 決定。combineReducers(...reducers) -
...middlewares:每個 middleware 接受 store 的 dispatch 和 getState 函數作為命名參數,並返回一個函數。-
該函數會被傳入被稱為 next 的下一個 middleware 的 dispatch 方法,並返回一個接受 action 的新函數,這個函數可以直接調用 next(action),或者在其他需要的時刻調用,也可不調用。
-
調用鏈的最后一個 middleware 會接受真實的 store 的 dispatch 方法作為 next 參數,並結束調用鏈。所以 middleware 的函數為 ({ getState, dispatch }) => next => action。
-
返回值:一個應用了 middleware 后的 store enhancer。這個store enhancer 就是一個函數,並且需要應用到 createStore。它會返回一個應用了 middleware 的新 createStore。
-
-
bindActionCreators:把 actionCreators 轉曾擁有同名 keys 的對象,讓 dispatch 把每個 actionCreator 包裝起來,這樣就可以直接調用它們。唯一使用 bindActionCreators 的場景是需要把 actionCreator 往下傳到一個組件上,卻不想讓這個組件察覺到 redux 的存在,而且不希望把 redux store 或者 dispatch 傳給它。// actionCreators:一個 actionCreators 或 鍵值是 actionCreators 的對象 // dispatch:一個 dispatch 函數, 由 store 提供 bindActionCreators(actionCreators, dispatch)返回值:一個與原對象類似的對象,只不過這個對象中的每個函數值都直接 dispatch action。如果傳入的是個函數,返回的也是函數。
-
compose(...fuctions):當需要多個 store 增強器 依次執行的時候使用它。compose 在應用常見的兩個用法:// 1 let buildStore = compose( applymiddleware(thunk) )(createStore) // 2 let initStore = compose( applymiddleware(thunk) )-
參數1(arguments):合成多個函數。每個函數接受一個函數作為參數,然后返回一個函數。
-
參數2(Function):從右往左把接受到的函數合成后的終極函數。
-
-
-
可能剛接觸,還不能很好理解,這邊我們換個方式來理解,如下圖:

- 更多關於
redux的內容(如 redux數據異步處理等)可前往 官方文檔 閱讀查看,這邊不講這么多,只要了解上面的這些就可以了。
react-redux 需要知道的那些事
-
終於進入正題了,為了在
react-native中使用redux,開發者提供了react-redux,基礎工作原理不變,只不過多了些方法和參數,所以這邊就需要繼續了解一下,以下內容整理自官方文檔: -
<Provider store>:使組件層級中的 connect() 方法能夠得到 redux store。正常情況下,我們的根組件應該嵌套在中才能使用 connect() 方法。 -
屬性(store):工程中唯一的 redux store。
-
屬性(children):組件層級的根組件。
-
-
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]):鏈接 react組件 和 redux store。
-
參數(mapStateToProps(state, [ownProps]): stateProps):定義了這個參數,組件會監聽 redux store 的變化,在任何情況下,只要 redux store 發送變化, mapStateToProps 函數就會被調用。也就是說:mapStateToProps負責返回需要傳遞給子組件的 state。
-
這個函數必須返回一個純對象,這個對象會與組件的props合並,如果省略這個參數,組件將監聽不到 redux store 。
-
如果指定改回調函數中的第二個參數 ownProps,這個參數的值為傳遞到組件的props,而且只要組件接到新的 props,mapStateToProps 也會被調用。
-
-
參數(mapDispatchToProps(dispatch, [ownProps]): dispatchProps):負責返回一個 dispatchProps,dispatchProps 是actionCreator的key和dispatch(action)的組合。
-
如果傳遞一個對象,那么每個定義在該對象的函數都將被當做 redux action creator,而且這個對象會與 redux store 綁定在一起,其中所定義的方法名將作為屬性名,合並到組件的 props 中。
-
如果傳遞的是一個函數,該函數將接收一個 dispatch 函數,然后由我們自己決定如何返回一個對象,這個對象通過 dispatch 函數與 action creator 以某種方式綁定在一起(提示:你也許會用到 Redux 的輔助函數bindActionCreators())。
-
如果你省略這個 mapDispatchToProps 參數,默認情況下,dispatch 會注入到你的組件 props 中。
-
如果指定了該回調函數中第二個參數 ownProps,該參數的值為傳遞到組件的 props,而且只要組件接收到新props,mapDispatchToProps 也會被調用。
-
-
參數(mergeProps(stateProps, dispatchProps, ownProps): props (Function)):如果指定了這個參數,mapStateToProps() 與 mapDispatchToProps() 的執行結果和組件自身的 props 將傳入到這個回調函數中。該回調函數返回的對象將作為 props 傳遞到被包裝的組件中。你也許可以用這個回調函數,根據組件的 props 來篩選部分的 state 數據,或者把 props 中的某個特定變量與 action creator 綁定在一起。如果你省略這個參數,默認情況下返回 Object.assign({}, ownProps, stateProps,dispatchProps) 的結果。
-
參數(options (Object)) 如果指定這個參數,可以定制 connector 的行為。
- [pure = true] (Boolean): 如果為 true,connector 將執行 shouldComponentUpdate 並且淺對比mergeProps 的結果,避免不必要的更新,前提是當前組件是一個“純”組件,它不依賴於任何的輸入或 state 而只依賴於 props 和 Redux store 的 state。默認值為 true。
- [withRef = false] (Boolean): 如果為 true,connector 會保存一個對被包裝組件實例的引用,該引用通過 getWrappedInstance() 方法獲得。默認值為 false。
-
返回值:根據配置信息,返回一個注入了 state 和 action creator 的 React 組件。
-
靜態屬性:WrappedComponent (Component): 傳遞到 connect() 函數的原始組件類。
-
靜態方法:組件原來的靜態方法都被提升到被包裝的 React 組件。
-
實例方法:getWrappedInstance(): ReactComponent;僅當 connect() 函數的第四個參數 options 設置了 { withRef: true } 才返回被包裝的組件實例。
-
-
注:
函數將被調用兩次。第一次是設置參數,第二次是組件與 Redux store 連接 connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)。
connect 函數不會修改傳入的 React 組件,返回的是一個新的已與 Redux store 連接的組件,而且你應該使用這個新組件。
mapStateToProps 函數接收整個 Redux store 的 state 作為 props,然后返回一個傳入到組件 props 的對象。該函數被稱之為 selector。參考使用 reselect 高效地組合多個 selector ,並對 收集到的數據進行處理。
bindActionCreators 的作用就是將 Actions 和 dispatch 組合起來生成 mapDispatchToProps 需要生成的內容。
- 是不是又懵圈了?那其實沒必要想得太復雜,只不過是組件這邊進行了2次包裝,其他並沒有太大的改變,這邊給各位客官又畫了張圖幫忙理解:

- 更多 react-redux 相關信息,請前往 Redux官方文檔 查閱。
使用前准備
-
使用
redux之前,我們還是需要配置一下是吧,很簡單,我們只需要執行以下步驟:-
使用
終端打開需要使用redux的工程主目錄:// 比如我們的 cd Desktop/Test -
導入
redux庫:npm install --save redux -
我喜歡直接介紹實用的,所以這邊我們要直接介紹
react-redux,不磨磨唧唧一大堆有的沒的,所以我們還需要:npm install --save react-redux -
這里先不講
中間件,盡量不然這些東西干擾我們。 -
好了,這樣我們就可以開始在
react-native中 使用redux了。
-
react-redux 使用
- 既然已經了解了redux和react-redux相關的東西,那這邊就通過一個小Demo來實際演練一下,UI結構如下:

- 首先,根據
redux官方文檔的示例我們可以看出官方建議我們將組件分成containers(容器組件)、components(模塊視圖組件)、redux三大塊。所以我們這邊文件的層級如下圖所示:

-
接着,我們再來完成視圖部分,然后根據視圖部分確定哪些需要 redux 支持,再來生成相應的
action與reducer文件。-
首先,是
Main文件,作為我們的容器組件放到containers文件夾內,Main中的內容:import React, { Component } from 'react'; import { StyleSheet, Text, View, TouchableOpacity, } from 'react-native'; export default class Main extends Component { render() { return ( <View style={styles.container}> {/* 需要改變的組件 */} {/* 按鈕 */} <TouchableOpacity> <Text>改變文字按鈕</Text> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, }); -
那里面我們需要將
Text作為視圖組件獨立出來,所以將視圖組件TestText放到components文件夾中,TestText中的內容:export default class TestText extends Component { render() { return ( <Text>Welcome to React Native</Text> ); } }
-
-
視圖部分我們搭建完成,那么我們接着就是確定需要哪些
action(行為),那前面提到了,我們是要點擊按鈕的時候讓文字發生改變,也就是說我們當前需要一個改變文字的行為,那我們就將這個行為命名為CHANGE_TEXT,那么我們需要初始化這個 action 這個對象,也就是前面我們提到的action creator:export const CHANGE_TEXT = 'CHANGE_TEXT'; // 初始化 CHANGE_TEXT 對象 export const changeText = (text) => { return { type: CHANGE_TEXT, text } }; -
action文件配置完畢后,我們就可以根據需求來編寫reducer文件了,reducer文件就是起到更新state的作用嘛,所以我們將改變 文字 的邏輯放到這里,當reducer匹配到當前的點擊行為為CHANGE_TEXT時,就執行相應的操作,返回一個新的state給我們使用,如果匹配不到,那么就默認返回一個不變的新state:import { CHANGE_TEXT, changeText } from '../action/action'; const mainReducer = (state = changeText('welcome to React Native'), action) => { const newState = state; const text = action.text; // 判斷 action 類型 switch (action.type) { case CHANGE_TEXT: return { ...newState, text: '改變了' + text }; default: return { ...newState, text:state.text } } }; export default mainReducer; -
配置完
action和reducer兩個文件后,緊接着我們就可以根據reducer來初始化store了:import Reducer from '../reducer/reducer'; import { createStore } from 'redux'; export default () => { // 根據 reducer 初始化 store const store = createStore(Reducer); return store; } -
redux的東西已經都配置完成了,接着就剩下使用了,所以接下來要解決的問題就是怎么發送行為,怎么接收state(狀態),上面提到了,store其實是個方法集,我們的發送行為 和 接收狀態方法都在store中,所以只要拿到store,所以只要拿到store就能進行這兩個操作。 -
那怎么拿到
store呢?在官方文檔中,清楚地告訴我們,Provider的任務就是將store傳給connect,而connect的作用是將我們的組件進行第二次包裝,將操作數據的函數和數據的狀態包裝到props中,所以,首先,我們需要對我們的Main文件進行第一次包裝,我們再新建一個index文件來對Main文件進行包裝:import React, { Component } from 'react'; // 引用外部文件 import { Provider } from 'react-redux'; import Main from './Main'; import configureStore from '../redux/store/store'; // 調用 store 文件中的 mainReducer常量中保存的方法 const store = configureStore(); export default class Root extends Component { render() { return( // 第一層包裝,為了讓 main 能夠拿到 store <Provider store={store}> <Main /> </Provider> ) } } -
包裝完成后,我們的
Main文件就可以獲得store了,那接着就是進行第二次包裝了,通過connect生成新組件:import React, { Component } from 'react'; import { StyleSheet, Text, View, TouchableOpacity, } from 'react-native'; import { connect } from 'react-redux'; import { changeText } from '../redux/action/action'; import TestText from '../components/TestText'; class Main extends Component { render() { // 通過 props 拿到保存的 onChangeText const { onChangeText } = this.props; return ( <View style={styles.container}> {/* 需要改變的組件 */} <TestText {...this.props} /> {/* 按鈕 */} <TouchableOpacity onPress={onChangeText} > <Text>改變文字按鈕</Text> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, }); // 獲取 state 變化 const mapStateToProps = (state) => { return { // 獲取 state 變化 } }; // 發送行為 const mapDispatchToProps = (dispatch) => { return { // 發送行為 } }; // 進行第二層包裝,生成的新組件擁有 接收和發送 數據的能力 export default connect(mapStateToProps, mapDispatchToProps)(Main); -
到這里,我們的 新組件 就能夠收發數據了,那怎么接收和發送呢,別急,我們接着就來完成
mapStateToProps(更新回調) 和 mapDispatchToProps(發送行為)兩個方法。首先,我們需要通過mapDispatchToProps來發送行為,然后通過mapStateToProps來監聽state的變化,這邊我們需要發送的行為type是CHANGE_TEXT,當發送行為之后,reducer就會去匹配 行為的類型,進行相應操作:// 發送行為 const mapDispatchToProps = (dispatch) => { return { onChangeText: () => dispatch(changeText('外部傳值')), } }; -
當
reducer接收到我們觸發的 行為 並進行一系列處理后,最終會返回一個新的state,那么 就會自動調用mapStateToProps來告訴系統,state被操作了,那么我們就可以通過mapStateToProps來獲取state狀態:// 獲取 state 變化 const mapStateToProps = (state) => { return { value: state.text, } }; -
那么接下來我們 怎么改變文字 呢?前面提到,connect 作用就是生成一個新的組件,新的組件的
props中包含了數據獲取和操作數據的函數,所以我們需要讓 子組件拿到容器組件中的props,然后在 子組件 中通過props就可以拿到上面 定義的value 和 onChangeText:export default class TestText extends Component { render() { // 獲取 props 中的 value const { value } = this.props; return ( // 根據 value 改變內部文字 <Text>{value}</Text> ); } } -
到這里,我們就能成功改變文字了。

小結論:
其實從上面的 demo 就可以看出,使用了
redux的項目變得比原本要復雜得多,原本幾句代碼就能搞定的事情現在要來個山路十八彎,這是因為 redux 是為了解決復雜工程而孕育的,所以不要為了使用 redux 而去使用它,使用之前需要權衡一下利弊,其中的好與壞只能自己慢慢體會。redux 對於剛入門的朋友來說確實比較繞,幫助理解的辦法就是多練,如果只看的話可能會越看越亂,所以還是建議多練,熟練之后就感覺沒什么了。
中間件
-
我個人認為 中間件 只需要注意 “順序” 就可以了。使用方法什么的在 中間件的說明文檔 中都講得很清楚。
-
關於 中間件 的使用,這邊就不多講了,因為可用的 中間件 很多,不可能一個一個講,等后面文章涉及哪些 中間件 再講。
