Redux入門0x105: 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 資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章