前言
- 本文
有配套視頻
,可以酌情觀看迎捺。 - 文中內(nèi)容因各人理解不同,可能會有所偏差查排,歡迎朋友們聯(lián)系我討論凳枝。
- 文中所有內(nèi)容僅供學習交流之用,不可用于商業(yè)用途跋核,如因此引起的相關法律法規(guī)責任岖瑰,與我無關,如文中內(nèi)容對您造成不便砂代,煩請聯(lián)系 277511806@qq.com 處理蹋订,謝謝。
- 轉(zhuǎn)載麻煩注明出處刻伊,謝謝露戒。
- 本篇資源:鏈接:https://pan.baidu.com/s/1jIY5aqq 密碼:C418
redux簡介
簡單來說,
redux
就是幫我們統(tǒng)一管理了react
組件的state
狀態(tài)捶箱。-
為什么要使用
redux
統(tǒng)一管理state
呢智什?沒有redux
我們依舊可以開發(fā) APP,但是當APP
的復雜度到達一定程度的時候丁屎,擺在我們面前的就是難以維護
的代碼(其中包含組件大量的異步回調(diào)荠锭,數(shù)據(jù)處理等等),但是使用redux
也會增加我們整個項目的復雜度悦屏,這就需要我們在兩者之間進行權(quán)衡了,對于這一部分键思,redux
開發(fā)者給我們下面幾個參考點:-
以下幾種情況不需要使用
redux
:整體 UI 很簡單础爬,沒有太多交互。
不需要與服務器進行大量交互吼鳞,也沒有使用 WebSocket看蚜。
視圖層只從單一來源獲取數(shù)據(jù)。
-
以下幾種情況可考慮使用
redux
:用戶的交互復雜赔桌。
根據(jù)層級用戶劃分功能供炎。
多個用戶之間協(xié)作。
與服務器大量交互疾党,或使用了 WebSocket音诫。
視圖層需要從多個來源獲取數(shù)據(jù)。
遇到 React 無法解決的問題雪位。
總結(jié)以上內(nèi)容:
redux
適用于 多交互竭钝,多數(shù)據(jù)源,復雜程度高的工程中。
-
也就是說香罐,當我們的組件出現(xiàn)
某個狀態(tài)需要共享
卧波,需要改變另一個組件狀態(tài)
等傳值比較不容易的情況。就可以考慮redux
庇茫,當然還有其他redux
的替代產(chǎn)品供我們使用港粱。
譯注:
WebSocket
:被稱為下一代客戶端與服務端的異步通信方法。取代了單個的TCP套接字旦签,使用ws或wss協(xié)議查坪,可用于任意的客戶端和服務器程序。WebSocket目前由W3C進行標準化顷霹。主要的優(yōu)點是服務器和客戶端可以彼此相互推送信息咪惠,允許跨域通信。
redux 必要知識
- 使用
redux
之前淋淀,基本的東西還是要都懂的遥昧,數(shù)據(jù)流向介紹:
-
Action:行為。它的作用就是將我們更新組件的
狀態(tài)(state)
的每個動作抽象為一個行為朵纷,它有一個必須的參數(shù)type
炭臭,定義了Action(行為)
的名稱,其他參數(shù)可自定義袍辞。寫法:{ type: 'TEST_ACTION', key1: 'value', ... keyN: value }
-
因為
Action
是個對象鞋仍,所以,我們需要創(chuàng)建這個對象搅吁,那創(chuàng)建這個對象的方法叫做ActionCreator
威创,寫法:function testAction(key1: ?string, ..., keyN: ?string) { return { type: "TEST_ACTION", key1: key1, ... keyN: keyN } }
-
Reducer:reducer 的作用就是根據(jù)傳入的 Action行為和舊的 state對象,返回一個新的 state 谎懦,然后組件會根據(jù) state 刷新肚豺。當我們確定了組件的 state 對象結(jié)構(gòu) 和 action 行為的時候就可以編寫 reducer 中的內(nèi)容。寫法:
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 合成統(tǒng)一管理吸申。
import { combineReducers } from 'redux'; import testReducer1 from './testReducer1'; import testReducer2 from './testReducer2'; export default = combineReducers({ testReducer1, testReducer2 });
-
reducer 是一個純函數(shù)(同樣的輸入,必須有同樣的輸出享甸,需要遵循 3 個約束):
不可修改傳入的參數(shù)截碴。
一定要干凈,沒有API請求蛉威,沒有變量修改日丹,單純執(zhí)行計算,沒有特殊情況蚯嫌。
調(diào)用非純函數(shù)(Date.now()聚凹、Math.random()等)割坠,每次都會得到不同結(jié)果導致數(shù)據(jù)錯誤等安全問題。
當傳入的 state 與 舊state 相比沒有區(qū)別妒牙,返回的 新state也應該一摸一樣彼哼。
-
Store:當 reducer 返回了新的 state 后,這個 state 怎么傳到組件和存儲就成了問題湘今,redux 就是把這個狀態(tài)統(tǒng)一放到 store 中進行管理敢朱。
import { createStore } from 'redux'; const store = createStore(reducers);
-
上面的代碼根據(jù) reducers 創(chuàng)建了一個 store方法集(它并不是一個對象),然后再 store 中提供一些方法供我們使用:
// 獲取當前 state store.getState() // 發(fā)送action,根據(jù)我們前面 注冊的reducers 處理state store.dispath(action) // 替換當前 state 中的 reducer store.replaceReducer(nextReducer) // 添加監(jiān)聽 store.subscribe(listener)
-
另外
redux
有 5個 全局方法:-
createStore
:創(chuàng)建一個readux store 來存儲應用中所有的state摩瞎,應用中只能存在一個 storecreateStore(reducer, [initialState],enhancer);
-
combineReducers
:把多個reducer函數(shù)作為value的object拴签,合并成一個reducers函數(shù),然后就可以通過reducers調(diào)用各個子reducer,state 對象的結(jié)構(gòu)由傳入的多個 reducer 的 key 決定旗们。combineReducers(...reducers)
-
...middlewares
:每個 middleware 接受 store 的 dispatch 和 getState 函數(shù)作為命名參數(shù)蚓哩,并返回一個函數(shù)。該函數(shù)會被傳入被稱為 next 的下一個 middleware 的 dispatch 方法上渴,并返回一個接受 action 的新函數(shù)岸梨,這個函數(shù)可以直接調(diào)用 next(action),或者在其他需要的時刻調(diào)用稠氮,也可不調(diào)用曹阔。
調(diào)用鏈的最后一個 middleware 會接受真實的 store 的 dispatch 方法作為 next 參數(shù),并結(jié)束調(diào)用鏈隔披。所以 middleware 的函數(shù)為 ({ getState, dispatch }) => next => action赃份。
返回值
:一個應用了 middleware 后的 store enhancer。這個store enhancer 就是一個函數(shù)奢米,并且需要應用到 createStore抓韩。它會返回一個應用了 middleware 的新 createStore。
-
bindActionCreators
:把 actionCreators 轉(zhuǎn)曾擁有同名 keys 的對象鬓长,讓 dispatch 把每個 actionCreator 包裝起來谒拴,這樣就可以直接調(diào)用它們。唯一使用 bindActionCreators 的場景是需要把 actionCreator 往下傳到一個組件上痢士,卻不想讓這個組件察覺到 redux 的存在彪薛,而且不希望把 redux store 或者 dispatch 傳給它茂装。// actionCreators:一個 actionCreators 或 鍵值是 actionCreators 的對象 // dispatch:一個 dispatch 函數(shù)怠蹂, 由 store 提供 bindActionCreators(actionCreators, dispatch)
-
返回值
:一個與原對象類似的對象,只不過這個對象中的每個函數(shù)值都直接 dispatch action少态。如果傳入的是個函數(shù)城侧,返回的也是函數(shù)。
-
-
compose(...fuctions)
:當需要多個 store 增強器 依次執(zhí)行的時候使用它彼妻。compose 在應用常見的兩個用法:// 1 let buildStore = compose( applymiddleware(thunk) )(createStore) // 2 let initStore = compose( applymiddleware(thunk) )
參數(shù)1(arguments):合成多個函數(shù)嫌佑。每個函數(shù)接受一個函數(shù)作為參數(shù)豆茫,然后返回一個函數(shù)。
參數(shù)2(Function):從右往左把接受到的函數(shù)合成后的終極函數(shù)屋摇。
-
可能剛接觸揩魂,還不能很好理解,這邊我們換個方式來理解炮温,如下圖:
- 更多關于
redux
的內(nèi)容(如 redux數(shù)據(jù)異步處理等)可前往 官方文檔 閱讀查看火脉,這邊不講這么多,只要了解上面的這些就可以了柒啤。
react-redux 需要知道的那些事
終于進入正題了倦挂,為了在
react-native
中使用redux
,開發(fā)者提供了react-redux
担巩,基礎工作原理不變方援,只不過多了些方法和參數(shù),所以這邊就需要繼續(xù)了解一下涛癌,以下內(nèi)容整理自官方文檔:-
<Provider store>
:使組件層級中的 connect() 方法能夠得到 redux store犯戏。正常情況下,我們的根組件應該嵌套在 <Provider> 中才能使用 connect() 方法祖很。屬性(store):工程中唯一的 redux store笛丙。
屬性(children):組件層級的根組件。
-
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]):鏈接 react組件 和 redux store假颇。
-
參數(shù)(mapStateToProps(state, [ownProps]): stateProps):定義了這個參數(shù)胚鸯,組件會監(jiān)聽 redux store 的變化,在任何情況下笨鸡,只要 redux store 發(fā)送變化姜钳, mapStateToProps 函數(shù)就會被調(diào)用。也就是說:mapStateToProps負責返回需要傳遞給子組件的 state形耗。
這個函數(shù)必須返回一個純對象哥桥,這個對象會與組件的props合并,如果省略這個參數(shù)激涤,組件將監(jiān)聽不到 redux store 拟糕。
如果指定改回調(diào)函數(shù)中的第二個參數(shù) ownProps,這個參數(shù)的值為傳遞到組件的props倦踢,而且只要組件接到新的 props送滞,mapStateToProps 也會被調(diào)用。
-
參數(shù)(mapDispatchToProps(dispatch, [ownProps]): dispatchProps):負責返回一個 dispatchProps辱挥,dispatchProps 是actionCreator的key和dispatch(action)的組合犁嗅。
如果傳遞一個對象,那么每個定義在該對象的函數(shù)都將被當做 redux action creator晤碘,而且這個對象會與 redux store 綁定在一起褂微,其中所定義的方法名將作為屬性名功蜓,合并到組件的 props 中。
如果傳遞的是一個函數(shù)宠蚂,該函數(shù)將接收一個 dispatch 函數(shù)式撼,然后由我們自己決定如何返回一個對象,這個對象通過 dispatch 函數(shù)與 action creator 以某種方式綁定在一起(提示:你也許會用到 Redux 的輔助函數(shù)bindActionCreators())求厕。
如果你省略這個 mapDispatchToProps 參數(shù)端衰,默認情況下,dispatch 會注入到你的組件 props 中甘改。
如果指定了該回調(diào)函數(shù)中第二個參數(shù) ownProps旅东,該參數(shù)的值為傳遞到組件的 props,而且只要組件接收到新props十艾,mapDispatchToProps 也會被調(diào)用抵代。
參數(shù)(mergeProps(stateProps, dispatchProps, ownProps): props (Function)):如果指定了這個參數(shù),mapStateToProps() 與 mapDispatchToProps() 的執(zhí)行結(jié)果和組件自身的 props 將傳入到這個回調(diào)函數(shù)中忘嫉。該回調(diào)函數(shù)返回的對象將作為 props 傳遞到被包裝的組件中荤牍。你也許可以用這個回調(diào)函數(shù),根據(jù)組件的 props 來篩選部分的 state 數(shù)據(jù)庆冕,或者把 props 中的某個特定變量與 action creator 綁定在一起康吵。如果你省略這個參數(shù),默認情況下返回 Object.assign({}, ownProps, stateProps,dispatchProps) 的結(jié)果访递。
-
參數(shù)(options (Object)) 如果指定這個參數(shù)晦嵌,可以定制 connector 的行為。
- [pure = true] (Boolean): 如果為 true拷姿,connector 將執(zhí)行 shouldComponentUpdate 并且淺對比mergeProps 的結(jié)果惭载,避免不必要的更新,前提是當前組件是一個“純”組件响巢,它不依賴于任何的輸入或 state 而只依賴于 props 和 Redux store 的 state描滔。默認值為 true。
- [withRef = false] (Boolean): 如果為 true踪古,connector 會保存一個對被包裝組件實例的引用含长,該引用通過 getWrappedInstance() 方法獲得。默認值為 false伏穆。
-
返回值:根據(jù)配置信息拘泞,返回一個注入了 state 和 action creator 的 React 組件。
靜態(tài)屬性:WrappedComponent (Component): 傳遞到 connect() 函數(shù)的原始組件類蜈出。
靜態(tài)方法:組件原來的靜態(tài)方法都被提升到被包裝的 React 組件田弥。
實例方法:getWrappedInstance(): ReactComponent涛酗;僅當 connect() 函數(shù)的第四個參數(shù) options 設置了 { withRef: true } 才返回被包裝的組件實例铡原。
-
注:
函數(shù)將被調(diào)用兩次偷厦。第一次是設置參數(shù),第二次是組件與 Redux store 連接 connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)燕刻。
connect 函數(shù)不會修改傳入的 React 組件只泼,返回的是一個新的已與 Redux store 連接的組件,而且你應該使用這個新組件卵洗。
mapStateToProps 函數(shù)接收整個 Redux store 的 state 作為 props请唱,然后返回一個傳入到組件 props 的對象。該函數(shù)被稱之為 selector过蹂。參考使用 reselect 高效地組合多個 selector 十绑,并對 收集到的數(shù)據(jù)進行處理。
bindActionCreators 的作用就是將 Actions 和 dispatch 組合起來生成 mapDispatchToProps 需要生成的內(nèi)容酷勺。
- 是不是又懵圈了本橙?那其實沒必要想得太復雜,只不過是組件這邊進行了2次包裝脆诉,其他并沒有太大的改變甚亭,這邊給各位客官又畫了張圖幫忙理解:
- 更多 react-redux 相關信息,請前往 Redux官方文檔 查閱击胜。
使用前準備
-
使用
redux
之前亏狰,我們還是需要配置一下是吧,很簡單偶摔,我們只需要執(zhí)行以下步驟:-
使用
終端
打開需要使用redux
的工程主目錄:// 比如我們的 cd Desktop/Test
-
導入
redux庫
:npm install --save redux
-
我喜歡直接介紹實用的暇唾,所以這邊我們要直接介紹
react-redux
,不磨磨唧唧一大堆有的沒的辰斋,所以我們還需要:npm install --save react-redux
這里先不講
中間件
信不,盡量不然這些東西干擾我們。好了亡呵,這樣我們就可以開始在
react-native
中 使用redux
了抽活。
-
react-redux 使用
- 既然已經(jīng)了解了redux和react-redux相關的東西,那這邊就通過一個小Demo來實際演練一下锰什,UI結(jié)構(gòu)如下:
- 首先下硕,根據(jù)
redux官方文檔的示例
我們可以看出官方建議我們將組件分成containers(容器組件)
、components(模塊視圖組件)
汁胆、redux
三大塊梭姓。所以我們這邊文件的層級如下圖所示:
-
接著,我們再來完成視圖部分嫩码,然后根據(jù)視圖部分確定哪些需要 redux 支持誉尖,再來生成相應的
action
與reducer
文件。-
首先铸题,是
Main
文件铡恕,作為我們的容器組件放到containers
文件夾內(nèi)琢感,Main
中的內(nèi)容: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
中的內(nèi)容:export default class TestText extends Component { render() { return ( <Text>Welcome to React Native</Text> ); } }
-
-
視圖部分我們搭建完成驹针,那么我們接著就是確定需要哪些
action(行為)
,那前面提到了诀艰,我們是要點擊按鈕的時候讓文字發(fā)生改變柬甥,也就是說我們當前需要一個改變文字的行為,那我們就將這個行為命名為CHANGE_TEXT
,那么我們需要初始化這個 action 這個對象其垄,也就是前面我們提到的action creator
:export const CHANGE_TEXT = 'CHANGE_TEXT'; // 初始化 CHANGE_TEXT 對象 export const changeText = (text) => { return { type: CHANGE_TEXT, text } };
-
action
文件配置完畢后苛蒲,我們就可以根據(jù)需求來編寫reducer
文件了,reducer
文件就是起到更新state
的作用嘛绿满,所以我們將改變 文字 的邏輯放到這里撤防,當reducer
匹配到當前的點擊行為為CHANGE_TEXT
時,就執(zhí)行相應的操作棒口,返回一個新的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
兩個文件后无牵,緊接著我們就可以根據(jù)reducer
來初始化store
了:import Reducer from '../reducer/reducer'; import { createStore } from 'redux'; export default () => { // 根據(jù) reducer 初始化 store const store = createStore(Reducer); return store; }
redux
的東西已經(jīng)都配置完成了漾肮,接著就剩下使用了,所以接下來要解決的問題就是怎么發(fā)送行為茎毁,怎么接收state(狀態(tài))
克懊,上面提到了,store
其實是個方法集七蜘,我們的發(fā)送行為 和 接收狀態(tài)
方法都在store
中谭溉,所以只要拿到store
,所以只要拿到store
就能進行這兩個操作橡卤。-
那怎么拿到
store
呢扮念?在官方文檔中,清楚地告訴我們碧库,Provider
的任務就是將store
傳給connect
柜与,而connect
的作用是將我們的組件進行第二次包裝,將操作數(shù)據(jù)的函數(shù)和數(shù)據(jù)的狀態(tài)包裝到props
中嵌灰,所以弄匕,首先,我們需要對我們的Main
文件進行第一次包裝沽瞭,我們再新建一個index
文件來對Main
文件進行包裝:import React, { Component } from 'react'; // 引用外部文件 import { Provider } from 'react-redux'; import Main from './Main'; import configureStore from '../redux/store/store'; // 調(diào)用 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 變化 } }; // 發(fā)送行為 const mapDispatchToProps = (dispatch) => { return { // 發(fā)送行為 } }; // 進行第二層包裝,生成的新組件擁有 接收和發(fā)送 數(shù)據(jù)的能力 export default connect(mapStateToProps, mapDispatchToProps)(Main);
-
到這里城丧,我們的 新組件 就能夠收發(fā)數(shù)據(jù)了延曙,那怎么接收和發(fā)送呢,別急芙贫,我們接著就來完成
mapStateToProps(更新回調(diào)) 和 mapDispatchToProps(發(fā)送行為)
兩個方法。首先傍药,我們需要通過mapDispatchToProps
來發(fā)送行為磺平,然后通過mapStateToProps
來監(jiān)聽state
的變化,這邊我們需要發(fā)送的行為type
是CHANGE_TEXT
,當發(fā)送行為之后国夜,reducer
就會去匹配 行為的類型谆甜,進行相應操作:// 發(fā)送行為 const mapDispatchToProps = (dispatch) => { return { onChangeText: () => dispatch(changeText('外部傳值')), } };
-
當
reducer
接收到我們觸發(fā)的 行為 并進行一系列處理后克蚂,最終會返回一個新的state
,那么 就會自動調(diào)用mapStateToProps
來告訴系統(tǒng)菠劝,state
被操作了,那么我們就可以通過mapStateToProps
來獲取state
狀態(tài):// 獲取 state 變化 const mapStateToProps = (state) => { return { value: state.text, } };
-
那么接下來我們 怎么改變文字 呢睁搭?前面提到赶诊,connect 作用就是生成一個新的組件,新的組件的
props
中包含了數(shù)據(jù)獲取和操作數(shù)據(jù)的函數(shù)园骆,所以我們需要讓 子組件拿到容器組件中的props
舔痪,然后在 子組件 中通過props
就可以拿到上面 定義的value 和 onChangeText:
export default class TestText extends Component { render() { // 獲取 props 中的 value const { value } = this.props; return ( // 根據(jù) value 改變內(nèi)部文字 <Text>{value}</Text> ); } }
到這里,我們就能成功改變文字了锌唾。
小結(jié)論:
其實從上面的 demo 就可以看出锄码,使用了
redux
的項目變得比原本要復雜得多,原本幾句代碼就能搞定的事情現(xiàn)在要來個山路十八彎
晌涕,這是因為 redux 是為了解決復雜工程而孕育的滋捶,所以不要為了使用 redux 而去使用它,使用之前需要權(quán)衡一下利弊余黎,其中的好與壞只能自己慢慢體會重窟。redux 對于剛?cè)腴T的朋友來說確實比較繞,幫助理解的辦法就是多練惧财,如果只看的話可能會越看越亂亲族,所以還是建議多練,熟練之后就感覺沒什么了可缚。
中間件
我個人認為 中間件 只需要注意 “順序” 就可以了霎迫。使用方法什么的在 中間件的說明文檔 中都講得很清楚。
關于 中間件 的使用帘靡,這邊就不多講了知给,因為可用的 中間件 很多,不可能一個一個講,等后面文章涉及哪些 中間件 再講涩赢。