文章目錄
redux 源碼分析
簡介
Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。
三大原則
- 單一數據源,創建一個 Redux store 來以存放應用中所有的 state。應用中應有且僅有一個 store。
- State 是隻讀的,唯一改變 state 的方法就是觸發 action,action.type 作爲邏輯說明。
- 使用純函數來執行修改,reducers 來描述 action 如何改變 state tree,reducers 應該是一個純函數。
對比 mobx
- 第一條,一般 redux 以 reducer 爲拆分維度,然後 combineReducers 合成一個總 reducer 來創建 store,然後傳給 React context,全局訪問。
而 mobx 一般對於一個頁面或頁面級別的組件創建 store,然後在一個總文件中導入,作爲一個大對象導出給 context。 - 第二條,redux 把 reducer 生產的 state 閉包在內部,無法直接訪問,只能 dispatch action 驅動 reducer 函數來改變 state,邏輯集中在 reducer 內,可預測維護。
而 mobx@observable state
可以隨意改變,甚至邏輯分散在視圖 view 層,例如onClick={() => (state.status = 1)}
。雖然官方建議把邏輯封裝
在 action 中,例如@action updata() {}
,但不是一種強約束。 - 第三條,這裏是唯一一個 redux 難以強約束用戶的地方,例如 reducer 中
state.xx = 1; return state
,應該是return { ...state, xx: 1}
。
而鑑於 mobx 的機制, mobx 更別說了。
所以常說,mobx 常用中小型應用,redux 更適合大型應用。
概覽
./src
├── applyMiddleware.ts
├── bindActionCreators.ts
├── combineReducers.ts
├── compose.ts
├── createStore.ts
└── index.ts
- index.ts,核心的 5 個文件名對應 redux 暴露的關鍵的 5 個函數。
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
}
- createStore,主函數用來創建 redux 實例,利用閉包隱藏數據,暴露接口操作內部數據。使用訂閱發佈模式,當數據改變時發佈通知。
- combineReducers,組合多個 reducer 函數,reducer 就是一個創建、修改重新創建數據的工廠。數據可以交給多個 reducer 小車間創建。
- bindActionCreators,當需要隱藏 dispatch 或 store 時用到,往往是傳給下一個組件時,讓組件無感知 redux 的存在。
- compose,工具函數
- applyMiddleware,利用 compose 來實現,store.dispatch 傳給最後一箇中間件函數,前一箇中間件函數能調用後一箇中間件函數。
使用
例子都是摘錄來的,不想看的可以跳過。
- createStore
import { createStore } from 'redux';
function reducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text]);
default:
return state
}
}
// createStore 至少接收一個參數、最多三個。第二個是初始化 state 對象,必須是一個純對象
let store = createStore(reducer, ['Use Redux']);
store.dispatch({
type: 'ADD_TODO',
text: 'Read the docs',
});
console.log(store.getState());
// [ 'Use Redux', 'Read the docs' ]
- combineReducers
// reducers/todos.js
export default function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text]);
default:
return state
}
}
// reducers/counter.js
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state
}
}
// reducers/index.js
import { combineReducers } from 'redux';
import todos from './todos';
import counter from './counter';
// 這裏 combineReducers 會根據傳入的 reducer 名稱設置爲 state 的屬性名
// 可以設置別名 combineReducers({ cnt: counter }); 最終 state = { cnt: 0 }
export default combineReducers({
todos,
counter
})
// App.js
import { createStore } from 'redux';
import reducer from './reducers/index';
let store = createStore(reducer);
console.log(store.getState());
// {
// counter: 0,
// todos: []
// }
store.dispatch({
type: 'ADD_TODO',
text: 'Use Redux'
});
console.log(store.getState());
// {
// counter: 0,
// todos: [ 'Use Redux' ]
// }
- bindActionCreators
// actionCreators.js
// action 函數
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
};
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
};
}
// App.js
const bindAction = bindActionCreators({ addTodo, removeTodo }, dispatch);
<Child {...bindAction} />
// Child.js
const {addTodo, removeTodo} = props.bindAction;
addTodo('text');
// 相當於 dispatch( addTodo('text') ); 子組件不知道 redux 的存在,卻驅動了數據的更新。
- applyMiddleware,當有多個參數的時,底層用 compose 來組合。
// dispatch 只接收一個純對象
function actionCreator( name ) { return { type: 'ActionName', name } }
store.dispatch(
actionCreator( '張三' )
);
// 但要根據用戶id 異步獲取姓名怎麼辦?
// 使用 redux-thunk 支持 dispatch 接收一個函數,
// 把 store 上 dispatch, getState 的控制權傳給該函數
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';
const reducer = combineReducers(reducers);
// applyMiddleware 爲 createStore 注入了 middleware:
const store = createStore(reducer, applyMiddleware(thunk));
function asyncAction(id) {
return (dispatch, getState) => ajax(id).then(name => dispatch( actionCreator(name) ));
}
// thunk 打破了 dispatch 只能接收純對象的約定
store.dispatch(
asyncAction(123)
);
深入 redux
createStore
- createStore 概覽,典型的發佈訂閱模式
function createStore(reducer, preloadedState, enhancer) {
// preloadedState、enhancer 不能同爲函數,enhancer 爲函數時,第四個參數不能爲函數
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.');
}
// preloadedState、enhancer 同爲函數時,preloadedState 複製給 enhancer,preloadedState 置爲 undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
// 增強函數 enhancer 不爲 undefined 時,執行增強函數,這個主要用來實現中間件的連接
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
// 增強函數
return enhancer(createStore)(reducer, preloadedState);
}
// reducer 不是函數拋錯
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
let currentReducer = reducer; // 存儲當前 reducer,也用於 replaceReducer 替換
let currentState = preloadedState; // 初始化 state
let currentListeners = []; // 訂閱的回調數組
let nextListeners = currentListeners; // 避免在發佈通知調用用戶回調函數時拋錯影響了數組
let isDispatching = false; // 防止在 reducer 中執行,getState、dispatch、subscribe、unsubscribe
// currentListeners 數組淺拷貝,防止 dispatch 階段執行用戶回調拋錯
// 導致 dispatch 中斷了,卻影響了原來的數組結構
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
// 獲取 currentState
function getState() {}
// 訂閱消息,即 listener 入 currentListeners 隊列
function subscribe(listener) {}
// 根據 action.type 執行 reducer 函數,觸發 state 更新,讓後發佈通知即遍歷 nextListeners 執行
function dispatch(action) {}
// 動態更換 reducer,然後根據新的 reducer 初始化一次 state
function replaceReducer(nextReducer) {}
// 爲了支持未來 Observable 新特性,還在提案階段,不討論
// function observable() {}
// type 爲一個隨機字符串不會和 reducer 中的任何 type 相匹配
// 如果 reducer 沒有默認匹配,則初始化的 state 爲 preloadedState
dispatch({ type: ActionTypes.INIT });
const store = {
getState,
subscribe,
dispatch,
replaceReducer,
};
return store;
}
- 解釋下 isDispatching 的作用,例如你在 reducer 中嘗試以下操作,都會拋錯
function reducer(state = [], action) {
// reducer 中執行 getState、dispatch、subscribe、unsubscribe
action.store && action.store.getState();
action.store && action.store.dispatch({
type: 'hello',
text: 'world',
});
action.store && action.store.subscribe(() => {});
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text]);
default:
return state
}
}
- getState
function getState() {
if (isDispatching) {
throw new Error('reducer 函數中不能執行 getState、dispatch、subscribe、unsubscribe');
}
return currentState; // 閉包隱藏內部 state,只能通過暴露 api 獲取
}
- subscribe
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.');
}
if (isDispatching) {
throw new Error('reducer 函數中不能執行 getState、dispatch、subscribe、unsubscribe');
}
let isSubscribed = true; // 註冊憑證
ensureCanMutateNextListeners(); // 確保原回調隊列在代碼拋錯時受到干擾
nextListeners.push(listener); // 發佈訂閱,必不可少的入隊列操作
// 返回取消訂閱函數
return function unsubscribe() {
if (!isSubscribed) { // 防止 unsubscribe 被多次執行
return;
}
if (isDispatching) {
throw new Error('reducer 函數中不能執行 getState、dispatch、subscribe、unsubscribe');
}
isSubscribed = false; // 一旦執行 unsubscribe 就註銷掉註冊憑證
ensureCanMutateNextListeners();
// 找到隊列中的函數,剔除
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null; // currentListeners 會在 dispatch 中恢復
};
}
- dispatch
function dispatch(action) {
if (!isPlainObject(action)) { // 什麼是純對象?1. 字面量定義對象:const obj = {}; 2. const obj = new Object();
throw new Error('action 必須是個純對象,異步操作需要自定義中間件');
}
if (typeof action.type === 'undefined') {
throw new Error('必不可少的 type ');
}
if (isDispatching) {
throw new Error('reducer 函數中不能執行 getState、dispatch、subscribe、unsubscribe');
}
try { // 防止用戶 reducer 拋錯
isDispatching = true; // isDispatching 用來控制 reducer 執行階段
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
// 發佈通知
const listeners = (currentListeners = nextListeners); // 在 unsubscribe 時 null,這裏被恢復
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
return action;
}
- replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('reducer 必須是個函數');
}
currentReducer = nextReducer; // 替換閉包中的 reducer
// type: ActionTypes.REPLACE 是個獨一無二的隨機字符
// 所有 reducer 中,最好有個默認 case
dispatch({ type: ActionTypes.REPLACE });
return store;
}
到此最重要的 createStore 函數就實現完畢。接下來分析兩個輔助函數 bindActionCreators,combineReducers,最後分析實現中間件功能的 compose、applyMiddleware。
bindActionCreators
function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') { // actionCreators 可以是單個函數
return bindActionCreator(actionCreators, dispatch);
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error("bindActionCreators 得是個對象或函數");
}
let boundActionCreators = {};
// 也可以是多個函數的導出,例如 export { actionCreators1, actionCreators2, ... }
for (const key in actionCreators) {
const actionCreator = actionCreators[key];
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreators;
}
// 實際上就是形成閉包對外隱藏 actionCreator, dispatch。
// 返回的函數接受 actionCreator 的參數,然後 dispatch。
function bindActionCreator(actionCreator, dispatch) {
return function (...args) {
return dispatch(actionCreator.apply(this, args));
};
}
combineReducers
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers);
const finalReducers = {};
// reducers 是一個包含多個 reducer 函數的對象
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
// 這裏會做很多判斷,爲了簡便刪了
// 例如你不能在 reducer 函數裏使用 redux 內部自定義的 ActionTypes.INIT
// reducer 函數默認都應該返回一個非 undefined 的值
// reducer 函數用戶明確知道不需要一個默認值,也應該用 null 代替 undefined
// 等等
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
const finalReducerKeys = Object.keys(finalReducers);
// 返回的是一個組合後的 reducer 函數
return function combination(state = {}, action) {
let hasChanged = false;
const nextState = {};
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
const previousStateForKey = state[key];
// 遍歷執行所有的 reducer
const nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
throw new Error('reducer 不應該給個 undefined');
}
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
// 如果其中有一個 reducer 返回的新的 state 有變更,則最終返回一個新的 state
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state
}
}
compose
- 輔助函數,效果:compose(fn1, fn2, fn3, …) fn1( fn2( fn3(…) ) ); 即 fn3 函數執行的返回值傳給 fn2,接着 fn2 的返回值傳給 fn1 執行。
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
applyMiddleware
function applyMiddleware(...middlewares) {
return (createStore) => (reducer, ...args) => {
const store = createStore(reducer, ...args);
let dispatch = () => {
throw new Error('中間件生成函數不能執行 dispatch 操作');
};
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
}
}
thunk 中間件
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
- 我們來串聯下,中間件是如何進行的
- const store = createStore(reducer, preloadedState, enhancer = applyMiddleware(thunk));
- 當 enhancer 存在時,執行 createStore 其實是執行 enhancer(createStore)(reducer, preloadedState);
- 所以 applyMiddleware 執行返回的是一個接受 createStore 的函數,返回一個新的 createStore 函數,接收 reducer, preloadedState;
- 最終返回一個 store 對象,看看中間件是如何接手管轄權的。
- 中間件需要是一個接收 store 對象的函數,遍歷執行中間件一遍生成 chain 這樣每個中間件就能閉包住 store;
- 接着在 compose 中執行了一次,最後一箇中間件通過 next 參數接收了 store.dispatch,返回了一個接受 action 的函數賦值給 store;
- 此時只有一個 thunk 中間件,如果傳一個函數的話,控制權就給了用戶,通過參數可以拿到 dispatch, getState。如果 action 是對象,此時 next === 內部的 store.dispatch ,就像原來一樣。
- 如果有多箇中間件,那麼
applyMiddleware(a, b)
,a 中的 next 就是 b 中返回的 action => {} 函數,b 中的 next 就是 store.dispatch。這樣就串起來了。
- 所以一般中間件的書寫的套路是 store => next => action => {};
總結
完。