React-Native 之 redux 與 react-redux

前言


  • 本文 有配套視頻,能夠酌情觀看。
  • 文中內容因各人理解不一樣,可能會有所誤差,歡迎朋友們聯繫我討論。
  • 文中全部內容僅供學習交流之用,不可用於商業用途,如所以引發的相關法律法規責任,與我無關,如文中內容對您形成不便,煩請聯繫 277511806@qq.com 處理,謝謝。
  • 轉載麻煩註明出處,謝謝。
  • 本篇資源:連接: https://pan.baidu.com/s/1cGjEua 密碼: scea

redux簡介


  • 簡單來講,redux 就是幫咱們統一管理了 react 組件的 state 狀態。html

  • 爲何要使用 redux 統一管理 state 呢?沒有 redux 咱們依舊能夠開發 APP,可是當 APP 的複雜度到達必定程度的時候,擺在咱們面前的就是 難以維護 的代碼(其中包含組件大量的異步回調,數據處理等等),可是使用 redux 也會增長咱們整個項目的複雜度,這就須要咱們在二者之間進行權衡了,對於這一部分,redux 開發者給咱們下面幾個參考點:react

    • 如下幾種狀況不須要使用 reduxnpm

      • 總體 UI 很簡單,沒有太多交互。redux

      • 不須要與服務器進行大量交互,也沒有使用 WebSocket。react-native

      • 視圖層只從單一來源獲取數據。api

    • 如下幾種狀況可考慮使用 redux跨域

      • 用戶的交互複雜。安全

      • 根據層級用戶劃分功能。服務器

      • 多個用戶之間協做。app

      • 與服務器大量交互,或使用了 WebSocket。

      • 視圖層須要從多個來源獲取數據。

      • 遇到 React 沒法解決的問題。

    • 總結以上內容:redux 適用於 多交互,多數據源,複雜程度高的工程中。

  • 也就是說,當咱們的組件出現 某個狀態須要共享須要改變另外一個組件狀態 等傳值比較不容易的狀況。就能夠考慮 redux ,固然還有其餘 redux 的替代產品供咱們使用。

譯註:

WebSocket:被稱爲下一代客戶端與服務端的異步通訊方法。取代了單個的TCP套接字,使用ws或wss協議,可用於任意的客戶端和服務器程序。WebSocket目前由W3C進行標準化。主要的優勢是服務器和客戶端能夠彼此相互推送信息,容許跨域通訊。

redux 必要知識


  • 使用 redux 以前,基本的東西仍是要都懂的,數據流向介紹:

redux工做流程.png

  • 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,應用中只能存在一個 store

      createStore(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):從右往左把接受到的函數合成後的終極函數。

  • 可能剛接觸,還不能很好理解,這邊咱們換個方式來理解,以下圖:

舉個栗子.png

  • 更多關於 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次包裝,其餘並無太大的改變,這邊給各位客官又畫了張圖幫忙理解:

Snip20170419_1.png

使用前準備


  • 使用 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結構以下:

UI結構.png

  • 首先,根據 redux官方文檔的示例 咱們能夠看出官方建議咱們將組件分紅 containers(容器組件)components(模塊視圖組件)redux 三大塊。因此咱們這邊文件的層級以下圖所示:

目錄結構.png

  • 接着,咱們再來完成視圖部分,而後根據視圖部分肯定哪些須要 redux 支持,再來生成相應的 actionreducer 文件。

    • 首先,是 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;
  • 配置完 actionreducer 兩個文件後,緊接着咱們就能夠根據 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 的變化,這邊咱們須要發送的行爲 typeCHANGE_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>
                );
            }
        }
  • 到這裏,咱們就能成功改變文字了。

效果演示.gif

小結論

  • 其實從上面的 demo 就能夠看出,使用了 redux 的項目變得比本來要複雜得多,本來幾句代碼就能搞定的事情如今要來個 山路十八彎 ,這是由於 redux 是爲了解決複雜工程而孕育的,因此不要爲了使用 redux 而去使用它,使用以前須要權衡一下利弊,其中的好與壞只能本身慢慢體會。

  • redux 對於剛入門的朋友來講確實比較繞,幫助理解的辦法就是多練,若是隻看的話可能會越看越亂,因此仍是建議多練,熟練以後就感受沒什麼了。

中間件


  • 我我的認爲 中間件 只須要注意 「順序」 就能夠了。使用方法什麼的在 中間件的說明文檔 中都講得很清楚。

  • 關於 中間件 的使用,這邊就很少講了,由於可用的 中間件 不少,不可能一個一個講,等後面文章涉及哪些 中間件 再講。

相關文章
相關標籤/搜索