1. 程式人生 > >Redux入門0x104: redux 中介軟體

Redux入門0x104: redux 中介軟體

0x000 概述

前一章講了reduxAction Creator,這一章講redux中很神奇的中介軟體。

0x001 手寫中介軟體

在專案中,我們經常會有記錄一些事件或者在某些事件發生的時候做某些事的需求,比如api介面鑑權操作、日誌記錄操作等,一般我們都可以用中介軟體來完成,中介軟體具有可拔插、可擴充套件的特點。我們也可以使用中介軟體來擴充套件redux的功能。

  • 記錄每一次的actionstate變化我們在之前是這麼使用redux的:

    import {createStore} from 'redux'
    
    function counter(state = 0, action) {
        switch (action.type) {
            case 'INCREMENT':
                return state + 1
            default:
                return state
        }
    }
    
    let store = createStore(counter)
    
    store.subscribe(() => {
        // console.log(store.getState())
    })
    
    const ACTION_INCREMENT = 'INCREMENT'
    
    const increment = () => {
        return {
            type: ACTION_INCREMENT
        }
    }
    const action = increment()
    store.dispatch(action)
    store.dispatch(action)
    store.dispatch(action)
    

    通過store.dispatch完成對資料的修改,現在我們希望記錄每一次對資料的修改,我們可以這麼做

    console.log('action', action.type)
    store.dispatch(action)
    console.log('next state', store.getState())
    
    console.log('action', action.type)
    store.dispatch(action)
    console.log('next state', store.getState())
    
    console.log('action', action.type)
    store.dispatch(action)
    console.log('next state', store.getState())

    效果很明顯,豬才會這麼做clipboard.png

    封裝1:封裝成函式,可以,經常我們也是這麼做的,但是不好

    const dispatch = (store, action) => {
        console.log('action', action.type)
        store.dispatch(action)
        console.log('next state', store.getState())
    }
    
    dispatch(store, action)

    封裝2:hackstore.dispatch,沒毛病,但是不夠優雅並且如果希望有多箇中間件不太好辦,並且希望中間鍵可以串聯起來

    const storeDispatch=store.dispatch
    store.dispatch= (action) => {
        console.log('action', action.type)
        storeDispatch(action)
        console.log('next state', store.getState())
    }
    store.dispatch(action)
    • 封裝3:多箇中間件串聯

      這裡寫了兩個中間鍵,一個是前中介軟體,一個是後中介軟體,在執行 before(store)的時候,其實我們已經將store.dispatch替換成了beforedispatch,所以我們在afterdispatch第二次替換的時候,const storeDispatch = store.dispatch中的 store.dispatch其實是before.dispatch,所以,當我們執行store.dispatch(increment())的時候,呼叫鏈其實是:store#dispatch=after#dispatch -> before#dispatch -> before#console.log -> store#dispatch -> after#console.log
      const before = (store) => {
          const storeDispatch = store.dispatch
          const dispatch=(action) => {
              console.log('before', action.type,store.getState())
              storeDispatch(action)
          }
          store.dispatch = dispatch
      }
      
      const after = (store) => {
          const storeDispatch = store.dispatch
          const dispatch = (action) => {
              storeDispatch(action)
              console.log('after',action.type,store.getState())
          }
          store.dispatch=dispatch
      }
      before(store)
      after(store)
      store.dispatch(increment())

      檢視輸出:

      clipboard.png

    • 封裝4:隱藏hack,減少樣板程式碼

      
      const before = (store) => {
          const storeDispatch = store.dispatch
          return (action) => {
              console.log('before', action.type, store.getState())
              storeDispatch(action)
          }
      }
      
      const after = (store) => {
          const storeDispatch = store.dispatch
          return (action) => {
              storeDispatch(action)
              console.log('after', action.type, store.getState())
          }
      }
      
      const applyMiddleware = (store, ...middlewares) => {
          middlewares.reverse()
          middlewares.forEach(middleware => {
              store.dispatch = middleware(store)
          })
      }
      
      applyMiddleware(store, before, after)
      
      store.dispatch(increment())
    • 封裝5:不使用 hack

      const before = (store) => {
          return (storeDispatch) => {
              return (action) => {
                  console.log('before', action.type, store.getState())
                  storeDispatch(action)
              }
          }
      }
      const after = (store) => {
          return (storeDispatch) => {
              return (action) => {
                  storeDispatch(action)
                  console.log('after', action.type, store.getState())
              }
          }
      }
      
      const applyMiddleware = (store, ...middlewares) => {
          middlewares.reverse()
          let storeDispatch = store.dispatch
          middlewares.forEach(middleware => {
              storeDispatch = middleware(store)(storeDispatch)
          })
          // store.dispatch = storeDispatch
          return {...store, ...{dispatch: storeDispatch}}
      }
      
      store = applyMiddleware(store, before, after)
      
      store.dispatch(increment())
    • 封裝6:優化中介軟體寫法

      const before = store => storeDispatch => action => {
          console.log('before', action.type, store.getState())
          return storeDispatch(action)
      }
      const after = store => storeDispatch => action => {
          let result = storeDispatch(action)
          console.log('after', action.type, store.getState())
          return result
      }
    • 最終的完整程式碼

      import {createStore} from 'redux'
      
      // reducer
      function counter(state = 0, action) {
          switch (action.type) {
              case 'INCREMENT':
                  return state + 1
              default:
                  return state
          }
      }
      // 建立 store
      let store = createStore(counter)
      
      // action
      const ACTION_INCREMENT = 'INCREMENT'
      
      // action creator
      const increment = () => {
          return {
              type: ACTION_INCREMENT
          }
      }
      
      // 前中介軟體
      const before = store => storeDispatch => action => {
          console.log('before', action.type, store.getState())
          return storeDispatch(action)
      }
      
      // 後中介軟體
      const after = store => storeDispatch => action => {
          let result = storeDispatch(action)
          console.log('after', action.type, store.getState())
          return result
      }
      
      // 應用中介軟體
      const applyMiddleware = (store, ...middlewares) => {
          middlewares.reverse()
          let storeDispatch = store.dispatch
          middlewares.forEach(middleware => {
              storeDispatch = middleware(store)(storeDispatch)
          })
          // store.dispatch = storeDispatch
          return {...store, ...{dispatch: storeDispatch}}
      }
      
      // 返回了新的 store
      store = applyMiddleware(store, before, after)
      
      // 發出 action 
      store.dispatch(increment())

0x002 redux applyMiddleware

前面寫了一個applyMiddleware方法,雖然可以用,但是官方其實也提供了這個方法,並且比我們寫的更好一點
const before = store => storeDispatch => action => {
    console.log('before', action.type, store.getState())
    return storeDispatch(action)
}
const after = store => storeDispatch => action => {
    let result = storeDispatch(action)
    console.log('after', action.type, store.getState())
    return result
}
let store = createStore(counter, applyMiddleware(before, after))
store.dispatch(increment())
可以看出來,相較於我們自己寫的`applyMiddleware`,官方提供的可以直接傳遞給`createStore`,而無需在次對`store`進行操作。

0x003 非同步action

const before = store => storeDispatch => action => {
    console.log('before', action.type, store.getState())
    return storeDispatch(action)
}
const after = store => storeDispatch => action => {
    let result = storeDispatch(action)
    console.log('after', action.type, store.getState())
    return result
}
const asyncAction=()=>{
    return (dispatch)=>{
        setInterval(()=>{
            dispatch(increment())
        },1000)
    }
}
const asyncMiddleware = store => storeDispatch => action => {
    if (typeof action === "function") {
        return action(storeDispatch)
    } else {
        return storeDispatch(action)
    }
}
let store = createStore(counter, applyMiddleware(asyncMiddleware,before,after))
store.dispatch(asyncAction())

這裡寫了一個asyncMiddleware,他判斷傳入的action是否是一個函式,如果是一個函式,那就直接執行這個函式,同時將dispatch作為引數,則在asyncAction我們就能直接訪問到dispatch了,就可以在asyncAction適當的時候再次dispatch了。當然一樣的,這樣的中介軟體也已經存在了:redux-thunk,日誌的中介軟體也已經存在了:redux-logger

import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'

const store = createStore(
    counter,
    applyMiddleware(
        thunkMiddleware,
        createLogger()
    )
)
store.dispatch(increment())

檢視效果

clipboard.png

0x004 資源