原文發布在我的個人博客 解讀只有99行的Redux(一) | 以太空間
一、概述
??隨著React這個極具革命式函數式思想的前端框架的誕生,Flux模式的前端狀態管理框架也隨之出現,其中比較著名的就是Flux、Redux和Mbox。Flux是Facebook開發的一種設計模式,旨在保持數據單向流動,當然Flux也存在一些小問題,所以Redux和其他的類Flux庫應運而生,它們在實現了Flux思想的同時又具備了自己的特點。
??Redux由Dan Abramov和Andrew Clark一起開發,因為Redux的緣故,它們都被邀請加入了Facebook的React團隊。在Dan Abramov的github gist頁面上有一個slim-redux(代碼附在本文末尾),去除了一些復雜的類型判斷和錯誤處理代碼,但完整地實現了Redux的所有功能,我們下面就來對這個只有99行的Redux進行解讀。
二、createStore
解讀
??我們使用Redux時候最頻繁使用的就是這個createStore
函數了,一般我們都會這樣使用
import { createStore } from 'redux';
const store = createStore(reducer);
??reducer
這個參數是用戶事先定義的數據狀態處理函數,一般會以類似下面的方式聲明:
// reducer接受state和action并返回新的state
function reducer (state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
];
default:
return state;
}
};
??reducer
的主體是一個switch
結構的運算,它只是根據傳入的狀態數據state
和action
來判斷返回一個新的state
。reducer
必須是一個純函數,純函數主要的含義就是它不可以修改影響輸入值,并且沒有副作用,副作用指的是例如函數中一些異步調用或者會影響函數作用域之外的變量一類的操作。
??另外,注意我們返回新的state時使用的展開操作符的方法,在上面的示例中,這樣返回的是一個全新的數組,而不是修改了傳入的數組。這也就是我們使用 React 技術棧時尤其需要注意的一點:保證數據的immutability不可變性。
??相信大家也知道,createStore
這個函數還有第二個的參數,是store
的初始狀態,也就是說craeteStore
函數原型是這樣的
function createStore(reducer, initialState) {...}
??然后再來看createStore
內部這段代碼:
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
return currentState;
}
??前兩行就是將傳入的reducer
參數和state
參數進行保存,第三行聲明了保存監聽函數的數組,第四行的isDispatching
是個標識型變量,具體用途下面會說到。
??我們知道,當我們調用createStore
函數之后,其返回結果(一般寫作store
)有個獲取當前store
內部數據狀態的成員函數getState
,該成員函數實現方法就是直接return內部的currentState
,so easy~
??接著看subscribe
函數的實現:
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
??當開發者對store
調用subscribe
函數的時候,其內部會把傳入的監聽函數listener
參數push到專門存放監聽函數的數組listeners
里,然后返回一個能取消傳入的監聽函數的函數unsubscribe
作為返回結果,這個unsubscribe
實現原理就是將listener
記錄下來,然后在調用的時候將其從listeners
數組里面剔除。
??接下來是最重量級選手dispatch
函數的實現代碼:
function dispatch(action) {
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(listener => listener());
return action;
}
??首先來解釋一下前面提到的isDispatching
,當開發者對store
派發(dispatch
)一個action
時,dispatch
內部會調用reducer
(currentReducer
)并利用傳入的action
對store
內部的狀態數據(currentState
)進行處理,但是這個過程可能是比較耗時的,所以為了避免對正在進行reducer
處理的store
再次進行reducer
處理,專門用isDispatching
來標識當前store
內部是否正在進行reducer
處理,如果isDispatching
為true
的話,就不會再對store
進行reducer
處理,起到一個加鎖的作用。
??所以disptach
內部會首先檢測isDispatching
是否正處于一個鎖死的狀態,如果isDispatching
為true
,說明鎖死,正在進行reducer
處理,就直接拋出錯誤。然后在try
塊內部,首先將isDispatching
置為true
,對其加鎖,再對store
內部的狀態數據進行處理,處理完后再把isDispatching
置為false
,將鎖打開。
??最后遍歷listeners
,調用所有的監聽函數,并返回action
。
??下面這部分是createStore
函數的剩余部分:
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: '@@redux/INIT' });
}
dispatch({ type: '@@redux/INIT' });
return { dispatch, subscribe, getState, replaceReducer };
??在調用createStore
的時候,其內部就派發(dispatch
)一個初始action({ type: '@@redux/INIT' }
),進行一些初始化的操作。此外,store
還有一個用的比較少的成員函數replaceReducer
,作用是替換reducer
函數,原理很簡單,就是將currentReducer
指向新的reducer
函數,并再次派發一個初始action
。
??上面這部分代碼的最后一行羅列出了store
所有成員函數。
??以下是解讀只有99行的Redux系列的其他兩篇文章