推薦使用並手寫實現redux-actions原理

@前端

1、前言

爲何介紹redux-actions呢?git

第一次見到主要是接手公司原有的項目,發現有以前的大佬在處理redux的時候引入了它。github

發現也確實 使得 在對redux的處理上方便了許多,而我爲了更好地使用一個組件或者插件,都會去去嘗試閱讀源碼並寫成文章 ,這個也不例外。redux

發現也確實有意思,推薦你們使用redux的時候也引入redux-actionssegmentfault

在這裏就介紹一下其使用方式,而且本身手寫實現一個簡單的redux-actions數組

2、介紹

在學習 redux 中,總以爲 action 和 reducer 的代碼過於呆板,好比函數

2.1 建立action

let increment = ()=>({type:"increment"})

2.2 reducer

let reducer = (state,action)=>{
    switch(action.type){
      case "increment":return {count:state.count+1};break;
      case "decrement":return {count:state.count-1};break;
      default:return state;
    }
}

2.3 觸發action

dispatch(increment())

綜上所示,咱們不免會以爲 increment 和 reducer 作一個小 demo 還行,遇到邏輯偏複雜的項目後,項目管理維護就呈現弊端了。因此最後的方式就是將它們獨立出來,同時在 reducer 中給與開發者更多的主動權,不能僅停留在數字的增增減減。學習

redux-actions主要函數有createAction、createActions、handleAction、handleActions、combineActions。spa

基本上就是隻有用到createActionhandleActionshandleAction插件

因此這裏咱們就只討論這三個個。

3、 認識與手寫createAction()

3.1 用法

通常建立Action方式:

let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}

使用createAction 建立 action

import { createAction } from 'redux-actions';
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
let objincrement = increment(10);// {type:"increment",paylaod:10}

咱們能夠看到

let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}

const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}

是等效的,那爲何不直接用傳統方式呢?

不難發現有兩點:

  1. 傳統方式,須要本身寫個函數來返回incrementObj,而利用封裝好的createAtion就不用本身寫函數
  2. 傳統方式,在返回的incrementObj如果有payload須要本身添加上去,這是多麼麻煩的事情啊,你看下面的代碼,如此的不方便。可是用了createAction返回的increment,咱們添加上payload,十分簡單,直接傳個參數,它就直接把它做爲payload的值了。
let increment = ()=>({type:"increment",payload:123})

3.2 原理實現

咱們先實現個簡單,值傳入 type參數的,也就是實現下面這段代碼的功能

const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}

咱們發現createAction('increment')()才返回最終的action對象。這不就是個柯里化函數嗎?

因此咱們能夠很是簡單的寫出來,以下面代碼所示,咱們把type類型看成action對象的一個屬性了

function createAction(type) {
    return () => {
        const action = {
            type
        };
        return action;
    };
}

好了如今,如今實現下面這個功能,也就是有payload的狀況

const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}

很明顯,這個payload是 在createAction('increment')返回的函數的參數,因此咱們垂手可得地給action添加上了payload。

function createAction(type) {
    return (payload) => {
        const action = {
            type,
            payload
        };
        return action;
    };
}

可是像第一種狀況咱們是不傳payload的,也就是說返回的action是不但願帶有payload的,可是這裏咱們寫成這樣就是 默認必定要傳入payload的了。

因此咱們須要添加個判斷,當不傳payload的時候,action就不添加payload屬性。

function createAction(type) {
    return (payload) => {
        const action = {
            type,
        };
        if(payload !== undefined){
            action.payload = payload
        }
        return action;
    };
}

在實際項目中我更喜歡下面這種寫法,但它是等價於上面這種寫法的

function createAction(type) {
    return (payload) => {
        const action = {
            type,
            ...payload?{payload}:{}
        };
        return action;
    };
}

其實createAction的參數除了type,還能夠傳入一個回調函數,這個函數表示對payload的處理。

const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}

像上面的代碼所示,咱們但願的是傳入10以後是返回的action中的payload是咱們傳入的2倍數

const increment = createAction('increment',(t)=> t * 2);
let objincrement = increment(10);// {type:"increment",paylaod:20}

如今,就讓咱們實現一下。

function createAction(type,payloadCreator) {
    return (payload) => {
        const action = {
            type,
        };
        if(payload !== undefined){
            action.payload = payloadCreator(payload)
        }
        return action;
    };
}

臥槽,太簡單了吧! 可是咱們又犯了前邊一樣的錯誤,就是咱們使用createAction的時候,不必定會傳入payloadCreator這個回調函數,因此咱們還須要判斷下

function createAction(type,payloadCreator) {
    return (payload) => {
        const action = {
            type,
        };
        if(payload !== undefined){
            action.payload = payloadCreator?payloadCreator(payload):payload
        }
        return action;
    };
}

臥槽,完美。

接下來看看 redux-action的 handleActions吧

4、認識handleActions

咱們先看看傳統的reducer是怎麼使用的

let reducer = (state,action)=>{
    switch(action.type){
      case "increment":return {count:state.count+1};break;
      case "decrement":return {count:state.count-1};break;
      default:return state;
    }
}

再看看使用了handleActions

const INCREMENT = "increment"
const DECREMENT = "decrement"
var reducer = handleActions({
    [INCREMENT]: (state, action) => ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) => ({
      counter: state.counter - action.payload
    })
},initstate)

這裏你們不要被{[DECREMENT]:(){}} 的寫法嚇住哈,就是把屬性寫成變量了而已。

咱們在控制檯 console.log(reducer) 看下結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kfrdU9BQ-1608209103080)(https://imgkr2.cn-bj.ufileos.com/736f323e-c4de-4439-92f2-51adf00e9185.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=VnvrRa9sDm%252B5TI9cp1Gvl0GwXBc%253D&Expires=1608280295)]

最後返回的就是一個 reducer 函數。

這樣就實現了 reducer 中功能化的自由,想寫什麼程序,咱們只要寫在

{[increment]:(state,action)=>{}}

這個函數內就行,同時也能夠把這些函數獨立成一個文件,再引入進來就行

import {increment,decrement}from "./reducers.js"
var initstate = {count:0}
var reducer = createReducer({
    [INCREMENT]: increment,
    [DECREMENT]: decrement
},initstate)

reducers.js

//reducers.js
export let increment = (state,action)=>({counter: state.counter + action.payload})
export let decrement = (state,action)=>({counter: state.counter - action.payload})

可見,

handleactions 能夠簡化 reducers 的寫法 不用那麼多 switch 並且能夠把函數獨立出來,這樣reducer就不再會有一大堆代碼了。

原本要講handleActions的實現了,可是在這以前,咱們必須先講一下handleAction,對,你仔細看,沒有s

5、認識與手寫實現handleAction

5.1 用法

看下使用方式

const incrementReducer = handleAction(INCREMENT, (state, action) => {
  return {counter: state.counter + action.payload}
}, initialState);

能夠看出來,跟handleActions的區別 就是,handleAction生成的reducer是專門來處理一個action的。

5.2 原理實現

若是你看過redux原理的話(若是你沒看過的話,推薦你去看下我以前的文章Redux 源碼解析系列(一) -- Redux的實現思想),相信你應該知道reducer(state,action)返回的結果是一個新的state,而後這個新的state會和舊的state進行對比,若是發現二者不同的話,就會從新渲染使用了state的組件,而且把新的state賦值給舊的state.

也就是說handleAction()返回一個reducer函數,而後incrementReducer()返回一個新的state。

先實現返回一個reducer函數

function handleAction(type, callback) {
    return (state, action) => {
      
    };
}

接下來應當是執行reducer(state,action)是時候返回state,也就是執行下面返回的這個

(state, action) => {
      
};

而其實就是執行callback(state) 而後返回一個新的 state

function handleAction(type, callback) {
    return (state, action) => {
        
      return callback(state)
    };
}

或許你會有疑問,爲何要這麼搞,而不直接像下面這樣,就少了一層包含。

function handleAction(state,type, callback) {
    return callback(state)
}

這纔是它的巧妙之處。它在handleAction()返回的reducer()時,可不必定會執行callback(state),只有handleAction傳入的type跟reducer()中傳入的action.type匹配到了纔會執行,不然就直接return state。表示沒有任何處理

function handleAction(type, callback) {
    return (state, action) => {
        
      return callback(state)
    };
}

所以咱們須要多加一層判斷

function handleAction(type, callback) {
    return (state, action) => {
        if (action.type !== type) {
            return state;
        }
        return callback(state)
    };
}

多麼完美啊!

好了如今咱們來實現下handleActions

6、handleActions原理實現

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type => {
        return handleAction(type, handlers[type]);
    });
    const reducer = reduceReducers(...reducers)
    return (state = defaultState, action) => reducer(state, action)
}

看,就這幾行代碼,是否是很簡單,不過應該很差理解,不過不要緊,我依舊將它講得粗俗易懂。

咱們拿上面用到的例子來說好了

var reducer = handleActions({
    [INCREMENT]: (state, action) => ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) => ({
      counter: state.counter - action.payload
    })
},initstate)
{
    [INCREMENT]: (state, action) => ({
      counter: state.counter + action.payload
    }),
    [DECREMENT]: (state, action) => ({
      counter: state.counter - action.payload
    })
}

上面這個對象,通過下面的代碼以後

const reducers = Object.keys(handlers).map(type => {
        return handleAction(type, handlers[type]);
    });

返回的reducer,其實就是

[
  handleAction(INCREMENT,(state, action) => ({
      counter: state.counter + action.payload
  })),
  handleAction(DECREMENT,(state, action) => ({
      counter: state.counter + action.payload
  })),
]

爲何要變成一個handleAction的數組,

我大概想到了,是想每次dispatch(action)的時候,就要遍歷去執行這個數組中的全部handleAction。

那豈不是每一個handleAction返回的reducer都要執行? 確實,可是別忘了咱們上面講到的,若是handleAction 判斷到 type和action.type 是不會對state進行處理的而是直接返回state

function handleAction(type, callback) {
    return (state, action) => {
        if (action.type !== type) {
            return state;
        }
        return callback(state)
    };
}

沒有即便每一個 handleAction 都執行了也不要緊

那應該怎麼遍歷執行,用map,forEach? 不,都不對。咱們看回源碼

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type => {
        return handleAction(type, handlers[type]);
    });
    const reducer = reduceReducers(...reducers)
    return (state = defaultState, action) => reducer(state, action)
}

使用了

const reducer = reduceReducers(...reducers)

用了reduceReducers這個方法,顧名思義,看這方法名,意思就是用reduce這個來遍歷執行reducers這個數組。也就是這個數組。

[
  handleAction(INCREMENT,(state, action) => ({
      counter: state.counter + action.payload
  })),
  handleAction(DECREMENT,(state, action) => ({
      counter: state.counter + action.payload
  })),
]

咱們看下reduceReducers的內部原理

function reduceReducers(...args) {
    const reducers = args;
    return (prevState, value) => {
        return reducers.reduce((newState, reducer, index) => {
            return reducer(newState, value);
        }, prevState);
    };
};

咱們發現將reducers這個數組放入reduceReducers,而後執行reduceReducers,就會返回

(prevState, value) => {
    return reducers.reduce((newState, reducer, index) => {
        return reducer(newState, value);
    }, prevState);
};

這個方法,也就是說執行這個方法就會 執行

return reducers.reduce((newState, reducer, index) => {
        return reducer(newState, value);
    }, prevState);

也就是會使用reduce遍歷執行reducers,爲何要用reduce來遍歷呢?

這是由於須要把上一個handleAction執行後返回的state傳遞給下一個。

這個思想有一點咱們之間以前講的關於compose函數的思想,感興趣的話,能夠去看一下【前端進階之認識與手寫compose方法】

function handleActions(handlers, defaultState) {
    const reducers = Object.keys(handlers).map(type => {
        return handleAction(type, handlers[type]);
    });
    const reducer = reduceReducers(...reducers)
    return (state = defaultState, action) => reducer(state, action)
}

如今也就是說這裏的reducer是reduceReducers(...reducers)返回的結果,也就

reducer = (prevState, value) => {
    return reducers.reduce((newState, reducer, index) => {
        return reducer(newState, value);
    }, prevState);
};

而handleActions返回

(state = defaultState, action) => reducer(state, action)

也就是說handleActions實際上是返回這樣一個方法。

(state = defaultState, action) => {
    return reducers.reduce((newState, reducer, index) => {
        return reducer(newState, value);
    }, state);
}

好傢伙,在handleAction之間利用reduce來傳遞state,真是個好方法,學到了。

貼一下github 的redux-action的源碼地址,感興趣的朋友能夠親自去閱讀一下,畢竟本文是作了簡化的 redux-actionshttps://github.com/redux-utilities/redux-actions

最後

文章首發於公衆號《前端陽光》,歡迎加入技術交流羣。

參考文章

相關文章
相關標籤/搜索