推薦使用並手寫實現redux-actions原理
阿新 • • 發佈:2020-12-30
@[toc]
## 一、前言
為什麼介紹**redux-actions**呢?
第一次見到主要是接手公司原有的專案,發現有之前的大佬在處理redux的時候引入了它。
發現也確實 使得 在對redux的處理上方便了許多,而我為了更好地使用一個元件或者外掛,都會去去嘗試閱讀原始碼並寫成文章 ,這個也不例外。
發現也確實有意思,推薦大家使用redux的時候也引入**redux-actions**
在這裡就介紹一下其使用方式,並且自己手寫實現一個簡單的**redux-actions**
## 二、介紹
在學習 redux 中,總覺得 action 和 reducer 的程式碼過於呆板,比如
### 2.1 建立action
```js
let increment = ()=>({type:"increment"})
```
### 2.2 reducer
```js
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
```js
dispatch(increment())
```
綜上所示,我們難免會覺得 increment 和 reducer 做一個小 demo 還行,遇到邏輯偏複雜的專案後,專案管理維護就呈現弊端了。所以最後的方式就是將它們獨立出來,同時在 reducer 中給與開發者更多的主動權,不能僅停留在數字的增增減減。
**redux-actions**主要函式有createAction、createActions、handleAction、handleActions、combineActions。
基本上就是隻有用到**createAction**,**handleActions**,**handleAction**
所以這裡我們就只討論這三個個。
## 三、 認識與手寫createAction()
### 3.1 用法
一般建立Action方式:
```js
let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}
```
使用createAction 建立 action
```js
import { createAction } from 'redux-actions';
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
let objincrement = increment(10);// {type:"increment",paylaod:10}
```
我們可以看到
```js
let increment = ()=>({type:"increment"})
let incrementObj = increment();// { type:"increment"}
```
與
```js
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
```
是等效的,那為什麼不直接用傳統方式呢?
不難發現有兩點:
1. 傳統方式,需要自己寫個函式來返回incrementObj,而利用封裝好的createAtion就不用自己寫函式
2. 傳統方式,在返回的incrementObj若是有payload需要自己新增上去,這是多麼麻煩的事情啊,你看下面的程式碼,如此的不方便。但是用了createAction返回的increment,我們新增上payload,十分簡單,直接傳個引數,它就直接把它作為payload的值了。
```js
let increment = ()=>({type:"increment",payload:123})
```
### 3.2 原理實現
我們先實現個簡單,值傳入 type引數的,也就是實現下面這段程式碼的功能
```js
const increment = createAction('increment');
let incrementObj = increment();// { type:"increment"}
```
我們發現`createAction('increment')()`才返回最終的action物件。這不就是個柯里化函式嗎?
所以我們可以非常簡單的寫出來,如下面程式碼所示,我們把type型別當作action物件的一個屬性了
```js
function createAction(type) {
return () => {
const action = {
type
};
return action;
};
}
```
好了現在,現在實現下面這個功能,也就是有payload的情況
```js
const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
```
很明顯,這個payload是 `在createAction('increment')`返回的函式的引數,所以我們輕而易舉地給action新增上了payload。
```js
function createAction(type) {
return (payload) => {
const action = {
type,
payload
};
return action;
};
}
```
但是像第一種情況我們是不傳payload的,也就是說返回的action是不希望帶有payload的,但是這裡我們寫成這樣就是 預設一定要傳入payload的了。
所以我們需要添加個判斷,當不傳payload的時候,action就不新增payload屬性。
```js
function createAction(type) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payload
}
return action;
};
}
```
在實際專案中我更喜歡下面這種寫法,但它是等價於上面這種寫法的
```js
function createAction(type) {
return (payload) => {
const action = {
type,
...payload?{payload}:{}
};
return action;
};
}
```
其實createAction的引數除了type,還可以傳入一個回撥函式,這個函式表示對payload的處理。
```js
const increment = createAction('increment');
let objincrement = increment(10);// {type:"increment",paylaod:10}
```
像上面的程式碼所示,我們希望的是傳入10之後是返回的action中的payload是我們傳入的2倍數
```js
const increment = createAction('increment',(t)=> t * 2);
let objincrement = increment(10);// {type:"increment",paylaod:20}
```
現在,就讓我們實現一下。
```js
function createAction(type,payloadCreator) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payloadCreator(payload)
}
return action;
};
}
```
臥槽,太簡單了吧! 但是我們又犯了前邊同樣的錯誤,就是我們使用createAction的時候,不一定會傳入payloadCreator這個回撥函式,所以我們還需要判斷下
```js
function createAction(type,payloadCreator) {
return (payload) => {
const action = {
type,
};
if(payload !== undefined){
action.payload = payloadCreator?payloadCreator(payload):payload
}
return action;
};
}
```
臥槽,完美。
接下來看看 redux-action的 handleActions吧
## 四、認識handleActions
我們先看看傳統的reducer是怎麼使用的
```js
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
```js
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)\]](https://img-blog.csdnimg.cn/20201217204623389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzk2NDE0OA==,size_16,color_FFFFFF,t_70)
最後返回的就是一個 reducer 函式。
這樣就實現了 reducer 中功能化的自由,想寫什麼程式,我們只要寫在
```js
{[increment]:(state,action)=>{}}
```
這個函式內就行,同時也可以把這些函式獨立成一個檔案,再引入進來就行
```js
import {increment,decrement}from "./reducers.js"
var initstate = {count:0}
var reducer = createReducer({
[INCREMENT]: increment,
[DECREMENT]: decrement
},initstate)
```
reducers.js
```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**
## 五、認識與手寫實現handleAction
### 5.1 用法
看下使用方式
```js
const incrementReducer = handleAction(INCREMENT, (state, action) => {
return {counter: state.counter + action.payload}
}, initialState);
```
可以看出來,跟handleActions的區別 就是,handleAction生成的reducer是專門來處理一個action的。
### 5.2 原理實現
如果你看過redux原理的話(如果你沒看過的話,推薦你去看下我之前的文章[Redux 原始碼解析系列(一) -- Redux的實現思想](https://mp.weixin.qq.com/s?__biz=MzU5NDM5MDg1Mw==&mid=2247488865&idx=1&sn=7e8f6a190288ca121ef747321cc02a6a&chksm=fe00af4bc977265d98f3e776507e32670805505f87f8d3a4a90a98fca272ef34dba7d06cf327&token=1118798341&lang=zh_CN&scene=21#wechat_redirect)),相信你應該知道reducer(state,action)返回的結果是一個新的state,然後這個新的state會和舊的state進行對比,如果發現兩者不一樣的話,就會重新渲染使用了state的元件,並且把新的state賦值給舊的state.
也就是說`handleAction()`返回一個reducer函式,然後`incrementReducer()`返回一個新的state。
先實現返回一個reducer函式
```js
function handleAction(type, callback) {
return (state, action) => {
};
}
```
接下來應當是執行reducer(state,action)是時候返回state,也就是執行下面返回的這個
```js
(state, action) => {
};
```
而其實就是執行callback(state) 然後返回一個新的 state
```js
function handleAction(type, callback) {
return (state, action) => {
return callback(state)
};
}
```
或許你會有疑問,為什麼要這麼搞,而不直接像下面這樣,就少了一層包含。
```js
function handleAction(state,type, callback) {
return callback(state)
}
```
這才是它的巧妙之處。它在handleAction()返回的reducer()時,可不一定會執行callback(state),只有handleAction傳入的type跟reducer()中傳入的action.type匹配到了才會執行,否則就直接return state。表示沒有任何處理
```js
function handleAction(type, callback) {
return (state, action) => {
return callback(state)
};
}
```
因此我們需要多加一層判斷
```js
function handleAction(type, callback) {
return (state, action) => {
if (action.type !== type) {
return state;
}
return callback(state)
};
}
```
多麼完美啊!
好了現在我們來實現下handleActions
## 六、handleActions原理實現
```js
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)
}
```
看,就這幾行程式碼,是不是很簡單,不過應該不好理解,不過沒關係,我依舊將它講得粗俗易懂。
我們拿上面用到的例子來講好了
```js
var reducer = handleActions({
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
},initstate)
```
```JavaScript
{
[INCREMENT]: (state, action) => ({
counter: state.counter + action.payload
}),
[DECREMENT]: (state, action) => ({
counter: state.counter - action.payload
})
}
```
上面這個物件,經過下面的程式碼之後
```js
const reducers = Object.keys(handlers).map(type => {
return handleAction(type, handlers[type]);
});
```
返回的reducer,其實就是
```js
[
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
```js
function handleAction(type, callback) {
return (state, action) => {
if (action.type !== type) {
return state;
}
return callback(state)
};
}
```
沒有即使每個 handleAction 都執行了也沒關係
那應該怎麼遍歷執行,用map,forEach? 不,都不對。我們看回原始碼
```js
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)
}
```
使用了
```js
const reducer = reduceReducers(...reducers)
```
用了reduceReducers這個方法,顧名思義,看這方法名,意思就是用reduce這個來遍歷執行reducers這個陣列。也就是這個陣列。
```js
[
handleAction(INCREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
handleAction(DECREMENT,(state, action) => ({
counter: state.counter + action.payload
})),
]
```
我們看下reduceReducers的內部原理
```js
function reduceReducers(...args) {
const reducers = args;
return (prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
};
```
我們發現將reducers這個陣列放入reduceReducers,然後執行reduceReducers,就會返回
```js
(prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
```
這個方法,也就是說執行這個方法就會 執行
```js
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
```
也就是會使用reduce遍歷執行reducers,為什麼要用reduce來遍歷呢?
這是因為需要把上一個handleAction執行後返回的state傳遞給下一個。
這個思想有一點我們之間之前講的關於compose函式的思想,感興趣的話,可以去看一下[【前端進階之認識與手寫compose方法】](https://mp.weixin.qq.com/s?__biz=MzU5NDM5MDg1Mw==&mid=2247489854&idx=1&sn=46a02276017ae2b232904de80071a4ea&chksm=fe00a314c9772a02da442086a1a62b174cb3b19f8252e0a79984bf127f5d388bf218ae0742ac&token=1009876451&lang=zh_CN#rd)
```js
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)`返回的結果,也就
```js
reducer = (prevState, value) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, prevState);
};
```
而handleActions返回
```js
(state = defaultState, action) => reducer(state, action)
```
也就是說handleActions其實是返回這樣一個方法。
```js
(state = defaultState, action) => {
return reducers.reduce((newState, reducer, index) => {
return reducer(newState, value);
}, state);
}
```
好傢伙,在handleAction之間利用reduce來傳遞state,真是個好方法,學到了。
貼一下github 的redux-action的原始碼地址,感興趣的朋友可以親自去閱讀一下,畢竟本文是做了簡化的 [redux-actions](https://github.com/redux-utilities/redux-actions):https://github.com/redux-utilities/redux-actions
## 最後
文章首發於公眾號《前端陽光》,歡迎加入技術交流群。
## 參考文章
- 【React系列---FSA知識】https://segmentfault.com/a/1190000010113847
- 【Redux-actions 的用法】https://zhuanlan.zhihu.com/p/273569290
- [【前端進階之認識與手寫compose方法】](https://mp.weixin.qq.com/s?__biz=MzU5NDM5MDg1Mw==&mid=2247489854&idx=1&sn=46a02276017ae2b232904de80071a4ea&chksm=fe00a314c9772a02da442086a1a62b174cb3b19f8252e0a79984bf127f5d388bf218ae0742ac&token=1009876451&lang=zh_CN#rd)
- [Redux 原始碼解析系列(一) -- Redux的實現思想](https://mp.weixin.qq.com/s?__biz=MzU5NDM5MDg1Mw==&mid=2247488865&idx=1&sn=7e8f6a190288ca121ef747321cc02a6a&chksm=fe00af4bc977265d98f3e776507e32670805505f87f8d3a4a90a98fca272ef34dba7d06cf327&token=1118798341&lang=zh_CN&scene=21#wechat_redi