寫在開始之前:
作爲一個記性不太好的人,學完的東西總是很容易忘記,因此需要反反覆覆得去學習和理解,堅持記錄是一個很好的習慣,也有助於後期自己回顧。
因水平有限,文章中部分內容可能存在錯誤,如果有誤,歡迎在評論區指出。
本篇文章對應的代碼在: https://github.com/YvetteLau/... 建議先 clone
代碼,然後對照代碼閱讀本文。
1. Redux
是什麼?
Redux
是 JavaScript
狀態容器,提供可預測化的狀態管理。Redux
除了和 React
一起用外,還支持其它界面庫。Redux
體小精悍,僅有 2KB
。這裏我們需要明確一點:Redux
和 React
之間,沒有強綁定的關係。本文旨在理解和實現一個 Redux
,但是不會涉及 react-redux
(一次深入理解一個知識點即可,react-redux
將出現在下一篇文章中)。
2. 從零開始實現一個 Redux
我們先忘記 Redux
的概念,從一個例子入手,使用 create-react-app
創建一個項目: toredux
。
代碼目錄: myredux/to-redux
中。
將 public/index.html
中 body
修改爲如下:
<div id="app">
<div id="header">
前端宇宙
</div>
<div id="main">
<div id="content">大家好,我是前端宇宙作者劉小夕</div>
<button class="change-theme" id="to-blue">Blue</button>
<button class="change-theme" id="to-pink">Pink</button>
</div>
</div>
我們要實現的功能如上圖所示,在點擊按鈕時,能夠修改整個應用的字體的顏色。
修改 src/index.js
如下(代碼: to-redux/src/index1.js
):
let state = {
color: 'blue'
}
//渲染應用
function renderApp() {
renderHeader();
renderContent();
}
//渲染 title 部分
function renderHeader() {
const header = document.getElementById('header');
header.style.color = state.color;
}
//渲染內容部分
function renderContent() {
const content = document.getElementById('content');
content.style.color = state.color;
}
renderApp();
//點擊按鈕,更改字體顏色
document.getElementById('to-blue').onclick = function () {
state.color = 'rgb(0, 51, 254)';
renderApp();
}
document.getElementById('to-pink').onclick = function () {
state.color = 'rgb(247, 109, 132)';
renderApp();
}
這個應用非常簡單,但是它有一個問題:state
是共享狀態,但是任何人都可以修改它,一旦我們隨意修改了這個狀態,就可以導致出錯,例如,在 renderHeader
裏面,設置 state = {}
, 容易造成難以預料的錯誤。
不過很多時候,我們又的確需要共享狀態,因此我們可以考慮設置一些門檻,比如,我們約定,不能直接修改全局狀態,必須要通過某個途經才能修改。爲此我們定義一個 changeState
函數,全局狀態的修改均由它負責。
//在 index.js 中繼續追加代碼
function changeState(action) {
switch(action.type) {
case 'CHANGE_COLOR':
return {
...state,
color: action.color
}
default:
return state;
}
}
我們約定只能通過 changeState
去修改狀態,它接受一個參數 action
,包含 type
字段的普通對象,type
字段用於識別你的操作類型(即如何修改狀態)。
我們希望點擊按鈕,可以修改整個應用的字體顏色。
//在 index.js 中繼續追加代碼
document.getElementById('to-blue').onclick = function() {
let state = changeState({
type: 'CHANGE_COLOR',
color: 'rgb(0, 51, 254)'
});
//狀態修改完之後,需要重新渲染頁面
renderApp(state);
}
document.getElementById('to-pink').onclick = function() {
let state = changeState({
type: 'CHANGE_COLOR',
color: 'rgb(247, 109, 132)'
});
renderApp(state);
}
抽離 store
儘管現在我們約定了如何修改狀態,但是 state
是一個全局變量,我們很容易就可以修改它,因此我們可以考慮將其變成局部變量,將其定義在一個函數內部(createStore
),但是在外部還需要使用 state
,因此我們需要提供一個方法 getState()
,以便我們在 createStore
獲取到 state
。
function createStore (state) {
const getState = () => state;
return {
getState
}
}
現在,我們可以通過 store.getState()
方法去獲取狀態(這裏需要說明的是,state
通常是一個對象,因此這個對象在外部其實是可以被直接修改的,但是如果深拷貝 state
返回,那麼在外部就一定修改不了,鑑於 redux
源碼中就是直接返回了 state
,此處我們也不進行深拷貝,畢竟耗費性能)。
僅僅獲取狀態是遠遠不夠的,我們還需要有修改狀態的方法,現在狀態是私有變量,我們必須要將修改狀態的方法也放到 createStore
中,並將其暴露給外部使用。
function createStore (state) {
const getState = () => state;
const changeState = () => {
//...changeState 中的 code
}
return {
getState,
changeState
}
}
現在,index.js
中代碼變成下面這樣(to-redux/src/index2.js
):
function createStore() {
let state = {
color: 'blue'
}
const getState = () => state;
function changeState(action) {
switch (action.type) {
case 'CHANGE_COLOR':
state = {
...state,
color: action.color
}
return state;
default:
return state;
}
}
return {
getState,
changeState
}
}
function renderApp(state) {
renderHeader(state);
renderContent(state);
}
function renderHeader(state) {
const header = document.getElementById('header');
header.style.color = state.color;
}
function renderContent(state) {
const content = document.getElementById('content');
content.style.color = state.color;
}
document.getElementById('to-blue').onclick = function () {
store.changeState({
type: 'CHANGE_COLOR',
color: 'rgb(0, 51, 254)'
});
renderApp(store.getState());
}
document.getElementById('to-pink').onclick = function () {
store.changeState({
type: 'CHANGE_COLOR',
color: 'rgb(247, 109, 132)'
});
renderApp(store.getState());
}
const store = createStore();
renderApp(store.getState());
儘管,我們現在抽離了 createStore
方法,但是顯然這個方法一點都不通用,state
和 changeState
方法都定義在了 createStore
中。這種情況下,其它應用無法複用此模式。
changeState
的邏輯理應在外部定義,因爲每個應用修改狀態的邏輯定然是不同的。我們將這部分邏輯剝離到外部,並將其重命名爲 reducer
(憋問爲什麼叫 reducer
,問就是爲了和 redux
保持一致)。reducer
是幹嘛的呢,說白了就是根據 action
的類型,計算出新狀態。因爲它不是在 createStore
內部定義的,無法直接訪問 state
,因此我們需要將當前狀態作爲參數傳遞給它。如下:
function reducer(state, action) {
switch(action.type) {
case 'CHANGE_COLOR':
return {
...state,
color: action.color
}
default:
return state;
}
}
createStore 進化版
function createStore(reducer) {
let state = {
color: 'blue'
}
const getState = () => state;
//將此處的 changeState 更名爲 `dispatch`
const dispatch = (action) => {
//reducer 接收老狀態和action,返回一個新狀態
state = reducer(state, action);
}
return {
getState,
dispatch
}
}
不同應用的 state
定然是不同的,我們將 state
的值定義在 createStore
內部必然是不合理的。
function createStore(reducer) {
let state;
const getState = () => state;
const dispatch = (action) => {
//reducer(state, action) 返回一個新狀態
state = reducer(state, action);
}
return {
getState,
dispatch
}
}
大家注意 reducer
的定義,在碰到不能識別的動作時,是直接返回舊狀態的,現在,我們利用這一點來返回初始狀態。
要想 state
有初始狀態,其實很簡單,咱們將初始的 state
的初始化值作爲 reducer
的參數的默認值,然後在 createStore
中派發一個 reducer
看不懂的動作就可以了。這樣 getState
首次調用時,可以獲取到狀態的默認值。
createStore 進化版2.0
function createStore(reducer) {
let state;
const getState = () => state;
//每當 `dispatch` 一個動作的時候,我們需要調用 `reducer` 以返回一個新狀態
const dispatch = (action) => {
//reducer(state, action) 返回一個新狀態
state = reducer(state, action);
}
//你要是有個 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是個狠人
dispatch({ type: `@@redux/__INIT__${Math.random()}` });
return {
getState,
dispatch
}
}
現在這個 createStore
已經可以到處使用了, 但是你有沒有覺得每次 dispatch
後,都手動 renderApp()
顯得很蠢,當前應用中是調用兩次,如果需要修改1000次 state
呢,難道手動調用 1000次 renderApp()
?
能不能簡化一下呢?每次數據變化的時候,自動調用 renderApp()
。當然我們不可能將 renderApp()
寫在 createStore()
的 dispatch
中,因爲其它的應用中,函數名未必叫 renderApp()
,而且有可能不止要觸發 renderApp()
。這裏可以引入 發佈訂閱模式
,當狀態變化時,通知所有的訂閱者。
createStore 進化版3.0
function createStore(reducer) {
let state;
let listeners = [];
const getState = () => state;
//subscribe 每次調用,都會返回一個取消訂閱的方法
const subscribe = (ln) => {
listeners.push(ln);
//訂閱之後,也要允許取消訂閱。
//難道我訂了某本雜誌之後,就不允許我退訂嗎?可怕~
const unsubscribe = () => {
listeners = listeners.filter(listener => ln !== listener);
}
return unsubscribe;
};
const dispatch = (action) => {
//reducer(state, action) 返回一個新狀態
state = reducer(state, action);
listeners.forEach(ln => ln());
}
//你要是有個 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是個狠人
dispatch({ type: `@@redux/__INIT__${Math.random()}` });
return {
getState,
dispatch,
subscribe
}
}
至此,一個最爲簡單的 redux
已經創建好了,createStore
是 redux
的核心。我們來使用這個精簡版的 redux
重寫我們的代碼,index.js
文件內容更新如下(to-redux/src/index.js
):
function createStore() {
//code(自行將上面createStore的代碼拷貝至此處)
}
const initialState = {
color: 'blue'
}
function reducer(state = initialState, action) {
switch (action.type) {
case 'CHANGE_COLOR':
return {
...state,
color: action.color
}
default:
return state;
}
}
const store = createStore(reducer);
function renderApp(state) {
renderHeader(state);
renderContent(state);
}
function renderHeader(state) {
const header = document.getElementById('header');
header.style.color = state.color;
}
function renderContent(state) {
const content = document.getElementById('content');
content.style.color = state.color;
}
document.getElementById('to-blue').onclick = function () {
store.dispatch({
type: 'CHANGE_COLOR',
color: 'rgb(0, 51, 254)'
});
}
document.getElementById('to-pink').onclick = function () {
store.dispatch({
type: 'CHANGE_COLOR',
color: 'rgb(247, 109, 132)'
});
}
renderApp(store.getState());
//每次state發生改變時,都重新渲染
store.subscribe(() => renderApp(store.getState()));
如果現在我們現在希望在點擊完 Pink
之後,字體色不允許修改,那麼我們還可以取消訂閱:
const unsub = store.subscribe(() => renderApp(store.getState()));
document.getElementById('to-pink').onclick = function () {
//code...
unsub(); //取消訂閱
}
順便說一句: reducer
是一個純函數(純函數的概念如果不瞭解的話,自行查閱資料),它接收先前的 state
和 action
,並返回新的 state
。不要問爲什麼 action
中一定要有 type
字段,這僅僅是一個約定而已(redux
就是這麼設計的)
遺留問題:爲什麼 reducer
一定要返回一個新的 state
,而不是直接修改 state
呢。歡迎在評論區留下你的答案。
前面我們一步一步推演了 redux
的核心代碼,現在我們來回顧一下 redux
的設計思想:
Redux
設計思想
-
Redux
將整個應用狀態(state
)存儲到一個地方(通常我們稱其爲store
) - 當我們需要修改狀態時,必須派發(
dispatch
)一個action
(action
是一個帶有type
字段的對象) - 專門的狀態處理函數
reducer
接收舊的state
和action
,並會返回一個新的state
- 通過
subscribe
設置訂閱,每次派發動作時,通知所有的訂閱者。
咱們現在已經有一個基礎版本的 redux
了,但是它還不能滿足我們的需求。我們平時的業務開發不會像上面所寫的示例那樣簡單,那麼就會有一個問題: reducer
函數可能會非常長,因爲 action
的類型會非常多。這樣肯定是不利於代碼的編寫和閱讀的。
試想一下,你的業務中有一百種 action
需要處理,把這一百種情況編寫在一個 reducer
中,不僅寫得人噁心,後期維護代碼的同事更是想殺人。
因此,我們最好單獨編寫 reducer
,然後對 reducer
進行合併。有請我們的 combineReducers
(和 redux
庫的命名保持一致) 閃亮登場~
combineReducers
首先我們需要明確一點:combineReducers
只是一個工具函數,正如我們前面所說,它將多個 reducer
合併爲一個 reducer
。combineReducers
返回的是 reducer
,也就是說它是一個高階函數。
我們還是以一個示例來說明,儘管 redux
不是非得和 react
配合,不過鑑於其與 react
配合最爲適合,此處,以 react
代碼爲例:
這一次除了上面的展示以外,我們新增了一個計數器功能( 使用 React
重構 ===> to-redux2
):
//現在我們的 state 結構如下:
let state = {
theme: {
color: 'blue'
},
counter: {
number: 0
}
}
顯然,修改主題和計數器是可以分割開得,由不同的 reducer
去處理是一個更好的選擇。
store/reducers/counter.js
負責處理計數器的state。
import { INCRENENT, DECREMENT } from '../action-types';
export default counter(state = {number: 0}, action) {
switch (action.type) {
case INCRENENT:
return {
...state,
number: state.number + action.number
}
case DECREMENT:
return {
...state,
number: state.number - action.number
}
default:
return state;
}
}
store/reducers/theme.js
負責處理修改主題色的state。
import { CHANGE_COLOR } from '../action-types';
export default function theme(state = {color: 'blue'}, action) {
switch (action.type) {
case CHANGE_COLOR:
return {
...state,
color: action.color
}
default:
return state;
}
}
每個 reducer
只負責管理全局 state
中它負責的一部分。每個 reducer
的 state
參數都不同,分別對應它管理的那部分 state
數據。
import counter from './counter';
import theme from './theme';
export default function appReducer(state={}, action) {
return {
theme: theme(state.theme, action),
counter: counter(state.counter, action)
}
}
appReducer
即是合併之後的 reducer
,但是當 reducer
較多時,這樣寫也顯得繁瑣,因此我們編寫一個工具函數來生成這樣的 appReducer
,我們把這個工具函數命名爲 combineReducers
。
我們來嘗試一下編寫這個工具函數 combineReducers
:
思路:
-
combineReducers
返回reducer
-
combineReducers
的入參是多個reducer
組成的對象 - 每個
reducer
只處理全局state
中自己負責的部分
//reducers 是一個對象,屬性值是每一個拆分的 reducer
export default function combineReducers(reducers) {
return function combination(state={}, action) {
//reducer 的返回值是新的 state
let newState = {};
for(var key in reducers) {
newState[key] = reducers[key](state[key], action);
}
return newState;
}
}
子 reducer
將負責返回 state
的默認值。比如本例中,createStore
中 dispatch({type:@@redux/__INIT__${Math.random()}
}),而傳遞給 createStore
的是 combineReducers(reducers)
返回的函數 combination
。
根據 state=reducer(state,action)
,newState.theme=theme(undefined, action)
, newState.counter=counter(undefined, action)
,counter
和 theme
兩個子 reducer
分別返回 newState.theme
和 newState.counter
的初始值。
利用此 combineReducers
可以重寫 store/reducers/index.js
import counter from './counter';
import theme from './theme';
import { combineReducers } from '../redux';
//明顯簡潔了許多~
export default combineReducers({
counter,
theme
});
我們寫的 combineReducers
雖然看起來已經能夠滿足我們的需求,但是其有一個缺點,即每次都會返回一個新的 state
對象,這會導致在數據沒有變化時進行無意義的重新渲染。因此我們可以對數據進行判斷,在數據沒有變化時,返回原本的 state
即可。
combineReducers 進化版
//代碼中省略了一些判斷,默認傳遞的參數均是符合要求的,有興趣可以查看源碼中對參數合法性的判斷及處理
export default function combineReducers(reducers) {
return function combination(state={}, action) {
let nextState = {};
let hasChanged = false; //狀態是否改變
for(let key in reducers) {
const previousStateForKey = state[key];
const nextStateForKey = reducers[key](previousStateForKey, action);
nextState[key] = nextStateForKey;
//只有所有的 nextStateForKey 均與 previousStateForKey 相等時,hasChanged 的值纔是 false
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
//state 沒有改變時,返回原對象
return hasChanged ? nextState : state;
}
}
applyMiddleware
官方文檔中,關於 applyMiddleware
的解釋很清楚,下面的內容也參考了官方文檔的內容:
日誌記錄
考慮一個小小的問題,如果我們希望每次狀態改變前能夠在控制檯中打印出 state
,那麼我們要怎麼做呢?
最簡單的即是:
//...
<button onClick={() => {
console.log(store.getState());
store.dispatch(actions.add(2));
}}>+</button>
//...
當然,這種方式肯定是不可取的,如果我們代碼中派發100次,我們不可能這樣寫一百次。既然是狀態改變時打印 state
,也是說是在 dispatch
之前打印 state
, 那麼我們可以重寫 store.dispatch
方法,在派發前打印 state
即可。
let store = createStore(reducer);
const next = store.dispatch; //next 的命令是爲了和中間件的源碼一致
store.dispatch = action => {
console.log(store.getState());
next(action);
}
崩潰信息
假設我們不僅僅需要打印 state
,還需要在派發異常出錯時,打印出錯誤信息。
const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致
store.dispatch = action => {
try{
console.log(store.getState());
next(action);
} catct(err) {
console.error(err);
}
}
而如果我們還有其他的需求,那麼就需要不停的修改 store.dispatch
方法,最後導致這個這部分代碼難以維護。
因此我們需要分離 loggerMiddleware
和 exceptionMiddleware
.
let store = createStore(reducer);
const next = store.dispatch; //next 的命名是爲了和中間件的源碼一致
const loggerMiddleware = action => {
console.log(store.getState());
next(action);
}
const exceptionMiddleware = action => {
try{
loggerMiddleware(action);
}catch(err) {
console.error(err);
}
}
store.dispatch = exceptionMiddleware;
我們知道,很多 middleware
都是第三方提供的,那麼 store
肯定是需要作爲參數傳遞給 middleware
,進一步改寫:
const loggerMiddleware = store => action => {
const next = store.dispatch;
console.log(store.getState());
next(action);
}
const exceptionMiddleware = store => action => {
try{
loggerMiddleware(store)(action);
}catch(err) {
console.error(err);
}
}
//使用
store.dispatch = exceptionMiddleware(store)(action);
現在還有一個小小的問題,exceptionMiddleware
中的 loggerMiddleware
是寫死的,這肯定是不合理的,我們希望這是一個參數,這樣使用起來才靈活,沒道理只有 exceptionMiddleware
需要靈活,而不管 loggerMiddleware
,進一步改寫如下:
const loggerMiddleware = store => next => action => {
console.log(store.getState());
return next(action);
}
const exceptionMiddleware = store => next => action => {
try{
return next(action);
}catch(err) {
console.error(err);
}
}
//使用
const next = store.dispatch;
const logger = loggerMiddleware(store);
store.dispatch = exceptionMiddleware(store)(logger(next));
現在,我們已經有了通用 middleware
的編寫格式了。
middleware
接收了一個 next()
的 dispatch
函數,並返回一個 dispatch
函數,返回的函數會被作爲下一個 middleware
的 next()
但是有一個小小的問題,當中間件很多的時候,使用中間件的代碼會變得很繁瑣。爲此,redux
提供了一個 applyMiddleware
的工具函數。
上面我們能夠看出,其實我們最終要改變的就是 dispatch
,因此我們需要重寫 store
,返回修改了 dispatch
方法之後的 store
.
所以,我們可以明確以下幾點:
-
applyMiddleware
返回值是store
-
applyMiddleware
肯定要接受middleware
作爲參數 -
applyMiddleware
要接受store
作爲入參,不過redux
源碼中入參是createStore
和createStore
的入參,想想也是,沒有必要在外部創建出store
,畢竟在外部創建出的store
除了作爲參數傳遞進函數,也沒有其它作用,不如把createStore
和createStore
需要使用的參數傳遞進來。
//applyMiddleWare 返回 store.
const applyMiddleware = middleware => createStore => (...args) => {
let store = createStore(...args);
let middle = loggerMiddleware(store);
let dispatch = middle(store.dispatch); //新的dispatch方法
//返回一個新的store---重寫了dispatch方法
return {
...store,
dispatch
}
}
以上是一個 middleware
的情況,但是我們知道,middleware
可能是一個或者是多個,而且我們主要是要解決多個 middleware
的問題,進一步改寫。
//applyMiddleware 返回 store.
const applyMiddleware = (...middlewares) => createStore => (...args) => {
let store = createStore(...args);
//順便說一句: redux 源碼中沒有直接把 store 傳遞過去,而是把 getState 和 dispatch 傳遞給了 middleware
let middles = middlewares.map(middleware => middleware(store));
//現在我們有多個 middleware,需要多次增強 dispatch
let dispatch = middles.reduceRight((prev, current) => prev(current), store.dispatch);
return {
...store,
dispatch
}
}
不知道大家是不是理解了上面的 middles.reduceRight
,下面爲大家細緻說明一下:
/*三個中間件*/
let logger1 = store => dispatch => action => {
console.log('111');
dispatch(action);
console.log('444');
}
let logger2 = store => dispatch => action => {
console.log('222');
dispatch(action);
console.log('555')
}
let logger3 = store => dispatch => action => {
console.log('333');
dispatch(action);
console.log('666');
}
let middle1 = logger1(store);
let middle2 = logger2(store);
let middle3 = logger3(store);
//applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)
//如果直接替換
store.dispatch = middle1(middle2(middle3(store.dispatch)));
觀察上面的 middle1(middle2(middle3(store.dispatch)))
,如果我們把 middle1
,middle2
,middle3
看成是數組的每一項,如果對數組的API比較熟悉的話,可以想到 reduce
,如果你還不熟悉 reduce
,可以查看MDN文檔。
//applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)
//reduceRight 從右到左執行
middles.reduceRight((prev, current) => current(prev), store.dispatch);
//第一次 prev: store.dispatch current: middle3
//第二次 prev: middle3(store.dispatch) current: middle2
//第三次 prev: middle2(middle3(store.dispatch)) current: middle1
//結果 middle1(middle2(middle3(store.dispatch)))
閱讀過 redux
的源碼的同學,可能知道源碼中是提供了一個 compose
函數,而 compose
函數中沒有使用 reduceRight
,而是使用的 reduce
,因而代碼稍微有點不同。但是分析過程還是一樣的。
compose.js
export default function compose(...funcs) {
//如果沒有中間件
if (funcs.length === 0) {
return arg => arg
}
//中間件長度爲1
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
}
關於 reduce
的寫法,建議像上面的 reduceRight
一樣,進行一次分析
使用 compose
工具函數重寫 applyMiddleware
。
const applyMiddleware = (...middlewares) => createStore => (...args) => {
let store = createStore(...args);
let middles = middlewares.map(middleware => middleware(store));
let dispatch = compose(...middles)(store.dispatch);
return {
...store,
dispatch
}
}
bindActionCreators
redux
還爲我們提供了 bindActionCreators
工具函數,這個工具函數代碼很簡單,我們很少直接在代碼中使用它,react-redux
中會使用到。此處,簡單說明一下:
//通常我們會這樣編寫我們的 actionCreator
import { INCRENENT, DECREMENT } from '../action-types';
const counter = {
add(number) {
return {
type: INCRENENT,
number
}
},
minus(number) {
return {
type: DECREMENT,
number
}
}
}
export default counter;
在派發的時候,我們需要這樣寫:
import counter from 'xx/xx';
import store from 'xx/xx';
store.dispatch(counter.add());
當然,我們也可以像下面這樣編寫我們的 actionCreator:
function add(number) {
return {
type: INCRENENT,
number
}
}
派發時,需要這樣編寫:
store.dispatch(add(number));
以上代碼有一個共同點,就是都是 store.dispatch
派發一個動作。因此我們可以考慮編寫一個函數,將 store.dispatch
和 actionCreator
綁定起來。
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
function bindActionCreators(actionCreator, dispatch) {
//actionCreators 可以是一個普通函數或者是一個對象
if(typeof actionCreator === 'function') {
//如果是函數,返回一個函數,調用時,dispatch 這個函數的返回值
bindActionCreator(actionCreator, dispatch);
}else if(typeof actionCreator === 'object') {
//如果是一個對象,那麼對象的每一項都要都要返回 bindActionCreator
const boundActionCreators = {}
for(let key in actionCreator) {
boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
}
return boundActionCreators;
}
}
在使用時:
let counter = bindActionCreators(counter, store.dispatch);
//派發時
counter.add();
counter.minus();
這裏看起來並沒有精簡太多,後面在分析 react-redux
時,會說明爲什麼需要這個工具函數。
至此,我的 redux
基本已經編寫完畢。與 redux
的源碼相比,還相差一些內容,例如 createStore
提供的 replaceReducer
方法,以及 createStore
的第二個參數和第三個參數沒有提及,稍微看一下代碼就能懂,此處不再一一展開。