banner
他山之石

他山之石

手摸手撸一個簡單的Redux(四)

理解 Redux 的原理有助於我們更好的使用它。本文實現 redux 的多個中間件合併功能。

image

在上一篇文章中實現了 redux 的中間件機制,支持了傳入一個中間件的用法,在實際的 redux 中,applyMiddleware 是支持傳入多個中間件的,本文使用 redux 實現多個中間件合併。

本文完整代碼請查看 Github:https://github.com/YanYuanFE/redux-app

// clone repo
git clone https://github.com/YanYuanFE/redux-app.git


cd redux-app

// checkout branch
git checkout part-6

// install
npm install

// start
npm start

中間件合併#

使用多個中間件的示例代碼如下:

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

還是在原來的項目中進行開發,在 src 下 redux.js 中,對 applyMiddleware 函數進行修改。

export function applyMiddleware(...middlewares) {
}

單個中間件 middleware 的結構如下:

store => next => action => {
 
 let result = next(action);
 
 return result;
};

當傳入多個 middlewares 參數時,將參數展開,middlewares 成為一個數組方便後面操作。

export function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args);
    let dispatch = store.dispatch;

    const midApi = {
      getState: store.getState(),
      dispatch: (...args) => dispatch(...args)
    }
    const middlewareChain = middlewares.map(middleware => middleware(midApi));
    dispatch = compose(...middlewareChain)(store.dispatch);
    return {
      ...store,
      dispatch
    }
  }
}

當有多個中間件時,對中間件數組 middlewares 執行 map 方法,對每個中間件都執行一次並傳入 midApi,返回成為一個新的數組 middlewareChain。
middlewareChain 中保存著 middleware 執行一次後返回的函數 [mid1, mid2, mid3],每個 mid 的結構如下:

next => action => {
 
 let result = next(action);
 
 return result;
};

然後,需要一個 compose 方法來對每個 mid 方法進行依次執行,並返回一個函數,最後傳入 store.dispatch 參數。
compose 方法作用如下:

compose(fn1, fn2, fn3)
fn1(fn2(fn3)))

compose 傳入一系列函數作為參數,然後將一系列函數參數嵌套依次進行調用。
下面是 compose 的實現:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((ret, item) => (...args) => ret(item(...args)))
}

在 compose 方法中,傳入一系列函數參數,展開,funcs 為一個數組。
當 funcs.length 為 0 時,即一個參數都沒有的時候,返回一個默認函數;當傳入一個參數時,直接返回第一個參數;當傳入多個函數參數時,使用數組的 reduce 方法依次對 funcs 數組從左到右執行一個函數,該函數中,ret 為上一次執行該函數的返回值,如果沒有指定初始值,第一次執行時為數組第一個參數,item 為當前正在處理的數組元素。
執行 compose (fn1, fn2, fn3),在 reduce 方法中的 ret 和 item 每次執行的結果如下:
第一次執行,ret 為 fn1,item 為 fn2, 返回 fn1(fn2());
第二次執行,ret 為 fn1(fn2()),item 為 fn3, 返回結果為 fn1 (fn2 (fn3())))。

到此,redux 現在已經支持多個中間件的用法了。

編寫中間件進行測試#

為了測試多個中間件,這裡我們再編寫一個簡單的中間件用於支持數組 action,在 src 目錄下新建 redux.array.js。
代碼如下:

const arrayThunk = ({dispatch, getState}) => next => action => {
  if (Array.isArray(action)) {
    action.forEach(v => dispatch(v))
  }
  return next(action)
}

export default arrayThunk;

上述代碼中,定義了 arrayThunk 中間件,使用 Array.isArray 方法判斷 action 是否為數組,如果是數組就遍歷 action 依次 dispatch。
下面在原來的計數器應用中,增加一個按鈕,使用 arrayThunk 中間件點擊每次加 2。
修改 components/Counter.js 如下:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.incrementAsync = this.incrementAsync.bind(this);
    this.incrementIfOdd = this.incrementIfOdd.bind(this);
  }

  incrementIfOdd() {
    if (this.props.value % 2 !== 0) {
      this.props.onIncrement();
    }
  }

  incrementAsync() {
    setTimeout(this.props.onIncrement, 1000);
  }

  render() {
    const { value, onIncrement, onDecrement, incrementAsync, addTwice } = this.props;
    console.log(this.props);
    return (
      <p>
        Clicked: {value} times
        {' '}
        <button onClick={onIncrement}>
          +
        </button>
        {' '}
        <button onClick={onDecrement}>
          -
        </button>
        {' '}
        <button onClick={incrementAsync}>
          Increment async
        </button>
        {' '}
        <button onClick={addTwice}>
          +2
        </button>
      </p>
    )
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncrement: PropTypes.func.isRequired,
  onDecrement: PropTypes.func.isRequired,
  incrementAsync: PropTypes.func.isRequired,
  addTwice: PropTypes.func.isRequired,
};

export default Counter;

在 Counter.js 中,增加一個按鈕,點擊觸發 props 中的 addTwice 方法。

在 src 下,App.js 中,修改代碼如下:

import React, { Component } from 'react';
import { connect } from './react-redux';
import Counter from './components/Counter';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    const { onIncrement, onDecrement, counter, incrementAsync, addTwice } = this.props;

    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <Counter
          value={counter}
          onIncrement={onIncrement}
          onDecrement={onDecrement}
          incrementAsync={incrementAsync}
          addTwice={addTwice}
        />
      </div>
    );
  }
}

const mapStateToProps = (state) => ({
  counter: state
});

function onIncrement() {
  return { type: 'INCREMENT' }
}

function addTwice() {
  return [{ type: 'INCREMENT' }, { type: 'INCREMENT' }]
}

function onDecrement() {
  return { type: 'DECREMENT' }
}

function incrementAsync() {
  return (dispatch, getState) => {
    setTimeout(() => {
      dispatch(onIncrement());
    }, 2000)
  }
}

const mapDispatchToProps = {
  onIncrement,
  onDecrement,
  incrementAsync,
  addTwice,
};

export default connect(mapStateToProps, mapDispatchToProps)(App);

增加了 addTwice 方法,發起一個數組 action,在數組中,定義了兩個增加計數的 action,並加入 mapDispatch 中,在 App 組件中,從 props 中獲取到 addTwice 方法並傳入 Counter 組件。

在 index.js 中,需要引入 arrayThunk 中間件。

import React from 'react';
import ReactDOM from 'react-dom';
import thunk from './thunk';
import arrThunk from './redux-array';
import { Provider } from './react-redux';
import './index.css';
import App from './App';
import {applyMiddleware, createStore} from './redux';

import counter from './reducers';


const store = createStore(counter, applyMiddleware(thunk, arrThunk));


ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

在 index.js 中,引入 arrayThunk 並傳入 applyMiddleware 中。

npm start 啟動項目,打開瀏覽器進行操作,結果如下,達到預期效果。

image

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。