Flux --> Redux --> Redux React 基礎實例教程

本文的目的很簡單,介紹Redux相關概念用法 及其在React項目中的基本使用javascript

 

假設你會一些ES6、會一些React、有看過Redux相關的文章,這篇入門小文應該能幫助你理一下相關的知識php

通常來講,推薦使用 ES6+React+Webpack 的開發模式,但Webpack須要配置一些東西,你能夠先略過,本文不須要Webpack基礎html

入門,只是一些基礎概念和用法的整理,更完整的內容推薦去看看文檔,英文中文java

(不過我我的認爲,官方文檔的例子相對來講太複雜了,很難讓新手立刻抓住重點)react

(官方的例子正統且聯繫業務,不一樣類型的操做或數據放在不一樣文件中,很規範,但也很繞,因此本文使用的例子很是簡單,且直接放在一個文件中 以便於理解)git

 

搭飛機前往:github

Flux思想Redux基本概念Redux的使用Redux在React中的使用(同步)Redux在React中的使用(異步,使用中間件)ajax

 

1、Flux

Flux是一種概念思想,或者說是一種應用架構npm

根據它的概念,一個應用中的數據流動應是單向的,且應用中的全部數據保存在一個位置,數據變化時保證視圖也同步變化,保證了數據和視圖的狀態是一一對應起來的json

此應用應該分爲四層:

  • view層:應用的視圖,頁面的(數據)展現
  • action層:(視圖)發出的某些動做,好比點擊事件
  • dispatcher層:派發器,接收action並處理這些動做,更新數據
  • store層:存放應用的數據,數據更新後,提醒view層更新視圖

它的概念思想可能一時半會理解不了,不要緊,過段時間就行了

 

2、Redux

上面說到,Flux只是一個思想,咱們能夠根據這個思想來本身實現出一個技術方案,來解決問題

是要解決什麼問題呢?

在使用React的過程當中,在組件間通訊的處理上咱們用了回調的方式,若是組件層級很深,不一樣組件間的數據交流就會致使回調及其觸發的函數很是多,代碼冗雜

須要一個狀態管理方案,方便管理不一樣組件間的數據,及時地更新數據

而Flux思想中的Store層,切合了這個問題

 

1. 什麼是Redux

Redux是受Flux啓發實現的一個技術方案,能夠認爲它是Flux的產物,但它並無沿用Flux全部的思想

主要區別是Flux的派發器dispatcher,Redux認爲使用派發器就得增長事件訂閱/發佈的規則,倒不如直接用函數調用的方式來得實在,簡單而統一,因此就將處理action的任務交給了store層(直接調用這個對象的dispatch方法)

2. 何時用Redux

Redux說簡單簡單,由於也就幾個API,理解好概念就好用了;說複雜也複雜,由於它將一個應用分紅了不一樣部分(action、處理action、store數據等),在正規的項目中是推薦將各部分區分到不一樣文件中的(如官方的例子),文件數量不少可能會比較難管理,固然,細粒化了也就減小了耦合度。最後還要加個操做把Redux的數據更新給React組件(若是用了React)

在大多數狀況下,Redux是不須要用的,如UI層很是簡單,沒有太多互動的

  • 用戶的使用方式很是簡單
  • 用戶之間沒有協做
  • 不須要與服務器大量交互,也沒有使用 WebSocket
  • 視圖層(View)只從單一來源獲取數據

 

而在多交互,多數據源的時候能夠考慮使用

  • 用戶的使用方式複雜
  • 不一樣身份的用戶有不一樣的使用方式(好比普通用戶和管理員)
  • 多個用戶之間能夠協做與服務器大量交互,或者使用了WebSocketView
  • 要從多個來源獲取數據

在須要管理複雜組件狀態的時候,能夠考慮使用

  • 某個組件的狀態,須要共享某個狀態
  • 須要在任何地方均可以拿到一個組件
  • 須要改變全局狀態一個組件
  • 須要改變另外一個組件的狀態

 

3. 開始用Redux

上面講了那麼多字,仍是看代碼來得實在

這裏先純粹講Redux,畢竟它和React是沒啥關係的

首先是環境配置,基本上都會使用ES6,因此Babel的支持是必須的

而後是Redux的支持,若是使用Webpack打包編譯,就用npm安裝個redux包

這裏採用直接在瀏覽器引入的方式,使用 這個庫

  <body>
        <div id="box"></div>

        <script type="text/javascript" src="../lib/react.js"></script>
        <script type="text/javascript" src="../lib/react-dom.js"></script>
        <script type="text/javascript" src="../lib/redux.min.js"></script>
        <script type="text/javascript" src="../build/reduxStart.js"></script>
    </body>

最後build裏的爲demo代碼用babel編譯以後的es5文件

在全局之中有Redux這個對象,取其中的幾個屬性來用

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;

3.1 Redux須要一個store來存放數據

這個store就由createStore建立

3.2 須要定義各個操做是什麼,即action

一般來講它是一個對象,包含type屬性表示是什麼操做,以及其餘屬性攜帶一些數據

它可能長這樣子,建議是遵循官方的 一些規範

let upAction = {
    type: 'UP'
};

咱們不止會傳type,還會傳一些值,若是傳不一樣的值就let一次就太冗雜了,通常來講就會用一個方法代替

let upAction = function(value) {
    return {
        type: 'up',
        value
    };
};

3.3 須要定義怎麼處理操做,在redux中它被稱做reducer

爲何把這種操做稱做reducer呢

redux引入了JS數組reduce方法的思想,JS的reduce長這樣

var arr = [1, 2, 3, 4];

var num = arr.reduce((a, b) => {
    return a + b;
});

num // 10

var num = arr.reduce((a, b) => {
    return a + b;
}, 5);

num // 15

固然了,只是看起來像,實際上差異挺大的,redux的reducer看起來像這樣

let upReducer = function(state = 0, action) {
    switch (action.type) {
        case 'up':
            return state + action.value;
        default:
            return state;
    }
};

它是一個函數,接收兩個參數,第一個參數爲數據(即某個狀態state),第二個參數爲action操做對象

爲了切合store中數據與view中視圖是一一對應的,reducer規定需始終返回新的state數據,不能直接在原有state中修改

而且,建議在匹配不到action的時候始終返回默認的state狀態,且建議在第一個參數中初始化默認的state值

 

3.4 在建立store的時候綁定reducer

redux基本上把全部操做都給了store,因此大部分方法都是用store來調用的

其實,你也能夠認爲Flux中的派發器(dispatcher)就是在裏面自動綁定的

let store = createStore(reducer);

// let store = createStore(reducer, 10);

如上,建立store的時候傳入reducer,能夠接收第二個參數表示reducer使用的默認值

3.5 視圖發出action動做

在某個時刻,發出了這些動做

store.dispatch(upAction(10));
store.dispatch(upAction(100));

3.6 使用store.getState()獲取store中的數據

3.7 動做發出後,reducer匹配動做更新store中的數據,視圖view層使用subscribe監聽數據的改變

store.subscribe(() => console.log(store.getState()));

 

來看一下完整的代碼

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;

let upAction = function(value) {
    return {
        type: 'up',
        value
    };
}

let upReducer = function(state = 0, action) {
    switch (action.type) {
        case 'up':
            return state + action.value;
        default:
            return state;
    }
};

let store = createStore(upReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

console.log(store.getState());

store.subscribe(() => console.log(store.getState()));

store.dispatch(upAction(10));
store.dispatch(upAction(100));

注意上面createStore中第二個參數是用於Redux DevTool的配置,即這個東西

使用這個工具能夠便於開發

看看上面代碼的輸出

初始獲取到的值爲0,兩次action後分別更新相關的數據狀態。若是加上初始默認值10

let store = createStore(upReducer, 10, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

 

3.8 使用多個reducer時,使用Redux的combineReducers方法

action固然不會只是up,多是down,這時能夠直接用switch語句切換;但若是action不是這裏增減的操做,放在一塊兒就有點亂套了

因此須要定義多個reducer,但createStore方法只接收一個reducer,因此就須要整合多個reducer爲一個,再統一傳入

它看起來像這樣

let reducer = combineReducers({upReducer, downReducer});
// let reducer = combineReducers({
//     upReducer: upReducer, 
//     downReducer: downReducer
// });

接收一個reducer組成的對象,屬性表示該reducer對應的state名(如state.upReducer),值表示這個reducer

固然,這個方法咱們能夠本身定義,看起來是這樣

let myCombineReducers = function(reducerObj) {
    let newState = {};

    return function(state = {}, action) {
        for (let item in reducerObj) {
            newState[item] = reducerObj[item](state[item], action);
        }

        return newState;
    }
};

其實就是遍歷reducer組,返回一個統一的新的reducer,且新的reducer中返回一個新的state

看看Redux中的實現,完整多了

 1 function combineReducers(reducers) {
 2       var reducerKeys = Object.keys(reducers);
 3       var finalReducers = {};
 4       for (var i = 0; i < reducerKeys.length; i++) {
 5         var key = reducerKeys[i];
 6 
 7         if (true) {
 8           if (typeof reducers[key] === 'undefined') {
 9             (0, _warning2['default'])('No reducer provided for key "' + key + '"');
10           }
11         }
12 
13         if (typeof reducers[key] === 'function') {
14           finalReducers[key] = reducers[key];
15         }
16       }
17       var finalReducerKeys = Object.keys(finalReducers);
18 
19       if (true) {
20         var unexpectedKeyCache = {};
21       }
22 
23       var sanityError;
24       try {
25         assertReducerSanity(finalReducers);
26       } catch (e) {
27         sanityError = e;
28       }
29 
30       return function combination() {
31         var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
32         var action = arguments[1];
33 
34         if (sanityError) {
35           throw sanityError;
36         }
37 
38         if (true) {
39           var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
40           if (warningMessage) {
41             (0, _warning2['default'])(warningMessage);
42           }
43         }
44 
45         var hasChanged = false;
46         var nextState = {};
47         for (var i = 0; i < finalReducerKeys.length; i++) {
48           var key = finalReducerKeys[i];
49           var reducer = finalReducers[key];
50           var previousStateForKey = state[key];
51           var nextStateForKey = reducer(previousStateForKey, action);
52           if (typeof nextStateForKey === 'undefined') {
53             var errorMessage = getUndefinedStateErrorMessage(key, action);
54             throw new Error(errorMessage);
55           }
56           nextState[key] = nextStateForKey;
57           hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
58         }
59         return hasChanged ? nextState : state;
60       };
61     }
View Code

 

加上個down操做,來看看完整代碼

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;

let upAction = function(value) {
    return {
        type: 'up',
        value
    };
}
let downAction = function(value) {
    return {
        type: 'down',
        value
    };
}

let upReducer = function(state = 0, action) {
    switch (action.type) {
        case 'up':
            return state + action.value;
        default:
            return state;
    }
};

let downReducer = function(state = 0, action) {
    switch (action.type) {
        case 'down':
            return state - action.value;
        default:
            return state;
    }
};

let reducer = combineReducers({upReducer, downReducer});

let store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

console.log(store.getState());

store.subscribe(() => console.log(store.getState()));

store.dispatch(upAction(10));
store.dispatch(upAction(100));
store.dispatch(downAction(10));
store.dispatch(downAction(100));

給reducer設個初始值,要注意的是,這個初始值是針對整個state的

若是隻有一個reducer,那reducer函數中的state就是這個state

若是用combineReducer整理了多個reducer,那各個reducer函數中的state是整個state中的reducer同名屬性的值

let reducer = combineReducers({upReducer, downReducer});

let store = createStore(
    reducer, 
    {upReducer: 10, downReducer: 10}, 
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

如上代碼定義了初始值,看看執行結果

 

四. 在React中使用Redux

Redux是一個獨立的技術方案,咱們將它運用到React項目中

接下來的問題主要有三個:

  • 如何將store中的數據同步給React組件
  • 如何讓React組件調用Redux的dispatch方法
  • 上面兩個

直接點,就是在React組件中調用Redux的subscribe方法來監聽同步數據,再在某個時機調用dispatch便可

但官方並不建議使用subscribe這個方法,而是建議使用封裝好的另外一個庫 React-Redux

 

4.1 引入庫

與引入Redux相似,你可使用Webpack引入包或瀏覽器直接引入這個庫

而後在全局window下能夠獲取到這個對象,取一些用到的屬性如

let {Provider, connect} = ReactRedux;

4.2 先定義一個有增加操做的React組件

class Increase extends Component {
    constructor(props) {
        super(props);
    }

    componentWillReceiveProps(nextProps) {
        console.log(nextProps);
    }

    increase() {
        let {dispatch} = this.props;
        dispatch({
            type: 'up'
        });
    }

    render() {
        return <p onClick={this.increase.bind(this)}>increase: {this.props.number}</p>
    }
}

組件定義了一個action,即點一次執行一次增加(increase)函數,裏面調用dispatch方法發出action,先看看其餘東西

4.3 定義一個reducer

function couterUp(state = {number: 100}, action) {
    switch (action.type) {
        case 'up':
            return {
                number: state.number + 1
            };
        default:
            return state;
    }
}

4.4 建立一個store

let store = createStore(couterUp, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

4.4 使用ReactRedux的connect方法

要將Redux中的數據同步給React,須要用到這個方法

它看起來像是這樣子

let APP = connect(
    mapStateToProps,
    mapDispatchToProps
)(Increase);

能夠把它當作是一個中間件,首先接收幾個參數完成配置階段,而後傳入React組件,包裝成一個新的東東(它並無直接修改Increase組件)

而通常來講,通常來講會傳入兩個參數(支持四個參數),顧名思義:

第一個參數(類型爲函數)

若是不傳或置入undefinednull,則表示不須要進行數據更新;不然表示將store中的數據經過props的形式傳給React組件

第二個參數(類型爲函數)

若是不傳或置入undefinednull,則表示將React-Redux中默認的dispatch方法傳給React組件;不然表示將redux中的dispatch發出動做經過props的形式傳給React組件

 

注意到上面的React組件代碼中,經過props獲取到了dispatch方法,而後自行發出動做

  increase() {
        let {dispatch} = this.props;
        dispatch({
            type: 'up'
        });
    }

若是要這樣作,mapDispatchToProps 這裏就不傳入了,即

let APP = connect(
    mapStateToProps
)(Increase);

用回常見的方式,在React組件中改一改,直接從props中獲取某個dispatch的發出動做

render() {
        return <p onClick={this.props.increase}>increase: {this.props.number}</p>
    }

同時修改兩個都傳入

let APP = connect(
    mapStateToProps,
    mapDispatchToProps
)(Increase);

4.5 mapStateToProps 和 mapDispatchToProps

咱們定義一下這兩個參數(函數),它看起來長這樣

function mapStateToProps(state) {
    return {
        number: state.number
    };
}

function mapDispatchToProps(dispatch) {
    return {
        increase: () => dispatch({
            type: 'up'
        })
    };
}

mapStateToProps 中第一個參數爲一個對象,表示store中總體的state數據

固然,第一個參數也能夠爲函數,也能夠接收第二個參數,表示自身擁有的屬性(ownProps),具體能夠看API

最後它返回了一個新的對象,表示要傳給React組件的數據

與mapStateToProps相似,mapDispatchToProps 也能夠接收兩個參數,

第一個表示當前的dispatch方法,第二個表示自身擁有的屬性(ownProps

最後它返回了一個action發出動做(一個函數),傳給React組件調用

 

4.6 使用Provider

基本好了,只差一步:將connect包裝組件後生成的新東東與實際頁面聯繫起來

使用ReactRedux提供的<Provider />,它看起來是這樣

render(
    <Provider store={store}>
        <APP />
    </Provider>,
    document.getElementById('box')
);

使用store屬性傳入上面的store對象

在children中置入有connect生成的APP組件,注意這裏只能包含一個父層

若是向其中傳入屬性,如

<APP name="app" />

那麼,mapStateToProps中的第二參數ownProps就能夠擁有這個name屬性

 

完整代碼

let {Component} = React;
let {render} = ReactDOM;
let {createStore, combineReducers} = Redux;
let {Provider, connect} = ReactRedux;

class Increase extends Component {
    constructor(props) {
        super(props);
    }

    componentWillReceiveProps(nextProps) {
        console.log(nextProps);
    }

    render() {
        return <p onClick={this.props.increase}>increase: {this.props.number}</p>
    }
}

function couterUp(state = {number: 100}, action) {
    switch (action.type) {
        case 'up':
            return {
                number: state.number + 1
            };
        default:
            return state;
    }
}

function mapStateToProps(state) {
    return {
        number: state.number
    };
}

function mapDispatchToProps(dispatch) {
    return {
        increase: () => dispatch({
            type: 'up'
        })
    };
}

let APP = connect(
    mapStateToProps,
    mapDispatchToProps
)(Increase);

let store = createStore(couterUp, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

render(
    <Provider store={store}>
        <APP />
    </Provider>,
    document.getElementById('box')
);
View Code

看一下運行結果

4.7 多個React組件中的使用

上面說的是單個React組件中的使用,實際使用中會有多個組件

多個組件的使用相似單個,只不過須要注意兩點

  • <Provider />中只能包含一個父級
  • mapStateToProps中第一個參數是指總體store中的數據

下面以兩個組件的栗子,看看如何實現

 

4.7.1 首先定義兩個組件,一增一減

class Increase extends Component {
    constructor(props) {
        super(props);
    }

    componentWillReceiveProps(nextProps) {
        console.log('increase: ', nextProps);
    }

    render() {
        return <p onClick={this.props.increase}>increase: {this.props.number}</p>
    }
}

class Decrease extends Component {
    constructor(props) {
        super(props);
    }

    componentWillReceiveProps(nextProps) {
        console.log('decrease: ', nextProps);
    }

    render() {
        return <p onClick={this.props.decrease}>decrease: {this.props.number}</p>
    }
}

4.7.2 定義對應的兩個reducer

function couterUp(state = {number: 100}, action) {
    switch (action.type) {
        case 'up':
            return {
                number: state.number + 1
            };
        default:
            return state;
    }
}

function counterDown(state = {number: -100}, action) {
    switch (action.type) {
        case 'down':
            return {
                number: state.number - 1
            };
        default:
            return state;
    }
}

4.7.3 建立store

let couter = combineReducers({
    couterUp,
    counterDown
});

let store = createStore(
    couter,
    {couterUp: {number: 10}},
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

4.7.4 建立鏈接兩個組件對應的兩個mapStateToProps 和 mapDispatchToProps

注意state爲整個store中的state,取值要取各reducer同名屬性如 state.couterUp

function mapStateToProps_1(state) {
    return {
        number: state.couterUp.number
    };
}

function mapDispatchToProps_1(dispatch) {
    return {
        increase: () => dispatch({
            type: 'up'
        })
    };
}

function mapStateToProps_2(state, props) {
    return {
        number: state.counterDown.number
    };
}

function mapDispatchToProps_2(dispatch) {
    return {
        decrease: () => dispatch({
            type: 'down'
        })
    };
}

4.7.5  各組件用connect包裝

let APP_1 = connect(
    mapStateToProps_1,
    mapDispatchToProps_1
)(Increase);

let APP_2 = connect(
    mapStateToProps_2,
    mapDispatchToProps_2
)(Decrease);

4.7.6  置入<Provider />中

注意只能有一個父級,因此得先簡單包裝一層

let APP = () => (
    <div>
        <APP_1 />
        <APP_2 name="APP_2"/>
    </div>
);

render(
    <Provider store={store}>
        <APP />
    </Provider>,
    document.getElementById('box')
);

完整代碼

  1 let {Component} = React;
  2 let {render} = ReactDOM;
  3 let {createStore, combineReducers} = Redux;
  4 let {Provider, connect} = ReactRedux;
  5 
  6 class Increase extends Component {
  7     constructor(props) {
  8         super(props);
  9     }
 10 
 11     componentWillReceiveProps(nextProps) {
 12         console.log('increase: ', nextProps);
 13     }
 14 
 15     render() {
 16         return <p onClick={this.props.increase}>increase: {this.props.number}</p>
 17     }
 18 }
 19 
 20 class Decrease extends Component {
 21     constructor(props) {
 22         super(props);
 23     }
 24 
 25     componentWillReceiveProps(nextProps) {
 26         console.log('decrease: ', nextProps);
 27     }
 28 
 29     render() {
 30         return <p onClick={this.props.decrease}>decrease: {this.props.number}</p>
 31     }
 32 }
 33 
 34 function couterUp(state = {number: 100}, action) {
 35     switch (action.type) {
 36         case 'up':
 37             return {
 38                 number: state.number + 1
 39             };
 40         default:
 41             return state;
 42     }
 43 }
 44 
 45 function counterDown(state = {number: -100}, action) {
 46     switch (action.type) {
 47         case 'down':
 48             return {
 49                 number: state.number - 1
 50             };
 51         default:
 52             return state;
 53     }
 54 }
 55 
 56 function mapStateToProps_1(state) {
 57     return {
 58         number: state.couterUp.number
 59     };
 60 }
 61 
 62 function mapDispatchToProps_1(dispatch) {
 63     return {
 64         increase: () => dispatch({
 65             type: 'up'
 66         })
 67     };
 68 }
 69 
 70 function mapStateToProps_2(state, props) {
 71     return {
 72         number: state.counterDown.number
 73     };
 74 }
 75 
 76 function mapDispatchToProps_2(dispatch) {
 77     return {
 78         decrease: () => dispatch({
 79             type: 'down'
 80         })
 81     };
 82 }
 83 
 84 let couter = combineReducers({
 85     couterUp,
 86     counterDown
 87 });
 88 
 89 let store = createStore(
 90     couter,
 91     {couterUp: {number: 10}},
 92     window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 93 );
 94 
 95 
 96 let APP_1 = connect(
 97     mapStateToProps_1,
 98     mapDispatchToProps_1
 99 )(Increase);
100 
101 let APP_2 = connect(
102     mapStateToProps_2,
103     mapDispatchToProps_2
104 )(Decrease);
105 
106 let APP = () => (
107     <div>
108         <APP_1 />
109         <APP_2 name="APP_2"/>
110     </div>
111 );
112 
113 render(
114     <Provider store={store}>
115         <APP />
116     </Provider>,
117     document.getElementById('box')
118 );
View Code

 

Good ! 完成了,看看結果

 

4.7.7  再看connect方法剩餘的兩個參數

connect方法接收可接收四個參數,上面已經談到了前兩個,後兩個不那麼經常使用

第三個參數,這裏很少說:[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)

第四個參數[options(Object)

這個options中有以下幾個屬性:

  • pure: true(默認)|false 表示是否在調用connect前三個參數的函數方法以前先檢測先後store中的值是否改變,改變才調用,不然不調用
  • areStatesEqual: 函數,當pure爲true時調用這個函數檢測是否相等,返回true|false表示是否相等,默認以嚴格相等===來判斷先後值是否相等
  • areOwnPropsEqual: 相似areStatesEqual,只不過它默認是用不嚴格相等==來判斷
  • areStatePropsEqual: 相似areStatesEqual,只不過它默認是用不嚴格相等==來判斷
  • areMergedPropsEqual: 相似areStatesEqual,只不過它默認是用不嚴格相等==來判斷

來看個例子,如今要手動的定義這個參數

針對Decrease,在減1時直接返回了false

let APP_2 = connect(
    mapStateToProps_2,
    mapDispatchToProps_2,
    null,
    {
        pure: true,
        areStatesEqual: (next, prev) => {
            console.log(next.counterDown, prev.counterDown);
            return next.counterDown.number < prev.counterDown.number;
        }
    }
)(Decrease);

能夠看到,減1的操做並無傳給Decrease組件,頁面沒有更新

 

順便看看有connect包裝後的組件

 

4.7.8 在React-Redux中使用異步

因Redux中操做的執行是同步的,若是要實現異步,好比某個操做用來發個異步請求獲取數據,就得引入中間件來處理這種特殊的操做

即這個操做再也不是普通的值,而是一個函數(如Promise異步),經過中間件的處理,讓Redux可以解析

 

先修改上面的栗子,在Increase組件中再也不是每次增長1,而是根據action中的value來指定,好比

function mapDispatchToProps_1(dispatch) {
    return {
        increase: () => dispatch({
            type: 'up',
            value: 10
        })
    };
}
function couterUp(state = {number: 100}, action) {
    switch (action.type) {
        case 'up':
            return {
                // number: state.number + 1
                number: state.number + action.value
            };
        default:
            return state;
    }
}

這裏定義了value是10,但假如value的值得由一個異步的請求才得出呢,要如何放進去

 

使用Redux提供的中間件applyMiddleware

let {createStore, combineReducers, applyMiddleware} = Redux;

這只是基礎的中間件apply函數,它幫助Redux將中間件包裝

如今來模擬一個異步請求

function mapDispatchToProps_1(dispatch) {
    return {
        // increase: () => dispatch({
        //     type: 'up',
        //     value: 10
        // })
        increase: () => dispatch(fetchIncreaseValue('redux-ajaxTest.php'))
    };
}

可一看到,dispatch中的action是一個函數(這個調用返回的仍是一個函數),而Redux默認只支持對象格式的action,因此這樣會報錯

這裏的fetchIncreaseValue看起來像這樣

function fetchIncreaseValue(url) {
    return function(dispatch) {
        return $.get(url).then(re => {
            re = JSON.parse(re);

            console.log(re);

            dispatch({
                type: 'up',
                value: re.value
            });
        })
    }
}

而請求後臺後返回值

<?php

    echo json_encode(array('value' => 100));

?>

能夠看到,異步獲取數據以後才執行dispatch發出操做,這裏須要一個dispatch關鍵字

爲了拿到這個關鍵字,得和thunkMiddleware搭配使用(讓這個方法可以在內層函數中使用),固然,你也能夠再搭配其餘中間件

若是使用Webpack打包,就安裝好 redux-thunk 包再 import 進來

這裏直接引入到瀏覽器中,引入這個庫,而後直接使用(注意這裏沒有 {}

let thunkMiddleware = window.ReduxThunk.default;

而後在建立store的時候,傳給redux的applyMiddleware便可

let store = createStore(
    couter,
    {couterUp: {number: 10}},
    applyMiddleware(thunkMiddleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

官方給的例子太複雜了,不過仍是去看看吧,我這裏抽出了主要的部分,

先來看看結果

 

使用這個Redux Dev Tool就得在createStore中配上最後一個參數,而createStore自身的某個參數又能給reducer設置初始值,且applyMiddleware也是在參數中定義

因此要注意的是:

若是用了這個Redux Dev Tool,就要保證applyMiddleware在第三個參數

let store = createStore(
    couter,
  // {}, applyMiddleware(thunkMiddleware), window.__REDUX_DEVTOOLS_EXTENSION__
&& window.__REDUX_DEVTOOLS_EXTENSION__() );

相似這樣省略第二個初始值參數,是會報錯的

把註釋去掉,放上一個空的初始便可,或者不用這個Dev Tool

let store = createStore(
    couter,
    applyMiddleware(thunkMiddleware)
);

能夠去看看其餘的Dev Tool

相關文章
相關標籤/搜索