前言
- 本文
有配套視頻
,可以酌情觀看。 - 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯系我討論。
- 文中所有內容僅供學習交流之用,不可用於商業用途,如因此引起的相關法律法規責任,與我無關,如文中內容對您造成不便,煩請聯系 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 對於剛入門的朋友來說確實比較繞,幫助理解的辦法就是多練,如果只看的話可能會越看越亂,所以還是建議多練,熟練之后就感覺沒什么了。
中間件
-
我個人認為 中間件 只需要注意 “順序” 就可以了。使用方法什么的在 中間件的說明文檔 中都講得很清楚。
-
關於 中間件 的使用,這邊就不多講了,因為可用的 中間件 很多,不可能一個一個講,等后面文章涉及哪些 中間件 再講。