必安裝套件:
相關套件
redux : Redux Toolkit 的運作是建立在 Redux 的基本架構之上的
immer.js : Redux Toolkit 在內部利用 Immer 來簡化狀態更新的邏輯,讓 Reducers 操作狀態變得更直觀。
在此文章中,只會簡單介紹這些概念,不會詳細說明如何使用,具體用法請參考官方文件。
以下的內容將會進行簡化處理,不會完整展示每一個細節,並且會去掉 type 定義以便於閱讀。如果有興趣深入了解,可以參考 原始碼,建議搭配實際的使用案例來更好地理解。
將 type 轉換成 { type, payload: ... }
的格式
function createAction(type, prepareAction) {
function actionCreator(...args) {
//如果有 prepareAction,處理 payload
if (prepareAction) {
const prepared = prepareAction(...args);
if (!prepared) {
throw new Error("prepareAction did not return an object");
}
return {
type,
payload: prepared.payload,
meta: prepared.meta,
error: prepared.error,
};
}
//如果沒有 prepareAction,傳入的第一個參數作為 payload
return { type, payload: args[0] };
}
actionCreator.toString = () => ${type};
actionCreator.type = type;
//比對傳入的 action 是否屬於此類型
actionCreator.match = (action) => action.type === type;
return actionCreator;
}
export function createReducer(
initialState,
mapOrBuilderCallback,
actionMatchers = [],
defaultCaseReducer
) {
// 針對不同的參數情況做處理
let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] =
typeof mapOrBuilderCallback === "function"
? executeReducerBuilderCallback(mapOrBuilderCallback)
: [mapOrBuilderCallback, actionMatchers, defaultCaseReducer];
// 確保初始狀態是不可變的,透過 immer 提供的 createNextState
let getInitialState;
if (isStateFunction(initialState)) {
getInitialState = () => createNextState(initialState(), () => {});
} else {
const frozenInitialState = createNextState(initialState, () => {});
getInitialState = () => frozenInitialState;
}
function reducer(state = getInitialState(), action) {
//根據 action 的類型從 actionsMap 獲取對應的 case reducer
let caseReducers = [
actionsMap[action.type],
...finalActionMatchers
.filter(({ matcher }) => matcher(action))
.map(({ reducer }) => reducer),
];
//如果沒有,則使用默認 case reducer
if (caseReducers.filter((cr) => !!cr).length === 0) {
caseReducers = [finalDefaultCaseReducer];
}
return caseReducers.reduce((previousState, caseReducer) => {
if (caseReducer) {
// isDraft是 immer 提供的function,用來確認是由 immer 產生的可變狀態
if (isDraft(previousState)) {
const draft = previousState;
const result = caseReducer(draft, action);
if (typeof result === "undefined") {
return previousState;
}
return result;
} else if (!isDraftable(previousState)) {
// 無法由immer代理,像是 primitive type,則直接使用原始的 previousState
const result = caseReducer(previousState, action);
//針對 undefined 的情況做處理
if (typeof result === "undefined") {
// 如果 previousState 是 null,直接返回它是合理的
if (previousState === null) {
return previousState;
}
throw Error(
"A case reducer on a non-draftable value must not return undefined"
);
}
return result;
} else {
return createNextState(previousState, (draft) => {
return caseReducer(draft, action);
});
}
}
return previousState;
}, state);
}
reducer.getInitialState = getInitialState;
//返回 reducer 並設置初始狀態函數
return reducer;
}
const reducer = createReducer(
{
counter: 0,
sumOfNumberPayloads: 0,
unhandledActions: 0,
},
(builder) => {
builder
.addCase(increment, (state, action) => {
state.counter += action.payload;
})
.addCase(decrement, (state, action) => {
state.counter -= action.payload;
})
.addMatcher(isActionWithNumberPayload, (state, action) => {})
.addDefaultCase((state, action) => {});
}
);
const increment = createAction("increment");
const decrement = createAction("decrement");
const counterReducer = createReducer(0, {
[increment]: (state, action) => state + action.payload,
[decrement]: (state, action) => state - action.payload,
});
function createSlice(options) {
// options裡面有包含 name、initialState、reducers
const { name } = options;
if (!name) {
throw new Error("`name` is a required option for createSlice");
}
const initialState =
typeof options.initialState == "function"
? options.initialState
: createNextState(options.initialState, () => {});
// createNextState是 immer 提供的方法 ,使用 createNextState 包裝,確保可以進行 immutable 的 state 操作
const reducers = options.reducers || {};
const reducerNames = Object.keys(reducers);
const sliceCaseReducersByName = {};
const sliceCaseReducersByType = {};
const actionCreators = {};
reducerNames.forEach((reducerName) => {
const maybeReducerWithPrepare = reducers[reducerName];
// 生成 action type,會是 `${name}/${reducerName}`
const type = getType(name, reducerName);
let caseReducer;
let prepareCallback;
// 如果是物件且包含reducer,那結構會是 { reducer, prepare }
if ("reducer" in maybeReducerWithPrepare) {
caseReducer = maybeReducerWithPrepare.reducer;
prepareCallback = maybeReducerWithPrepare.prepare;
} else {
caseReducer = maybeReducerWithPrepare;
}
sliceCaseReducersByName[reducerName] = caseReducer;
sliceCaseReducersByType[type] = caseReducer;
// 如果有prepareCallback,則會處理payload
// 可參考上方createAction
actionCreators[reducerName] = prepareCallback
? createAction(type, prepareCallback)
: createAction(type);
});
// 生成reducer
function buildReducer() {
const [
extraReducers = {},
actionMatchers = [],
defaultCaseReducer = undefined,
] =
typeof options.extraReducers === "function"
? executeReducerBuilderCallback(options.extraReducers)
: [options.extraReducers];
// 傳入extraReducers
const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType };
// 使用 createReducer 創建最終的 reducer,可參考上方的 createReducer
return createReducer(
initialState,
finalCaseReducers,
actionMatchers,
defaultCaseReducer
);
}
let _reducer; //最終的 reducer
return {
name,
// 可以傳到configureStore
reducer(state, action) {
if (!_reducer) _reducer = buildReducer();
return _reducer(state, action);
},
// 讓dispatch使用
actions: actionCreators,
caseReducers: sliceCaseReducersByName,
getInitialState() {
if (!_reducer) _reducer = buildReducer();
return _reducer.getInitialState();
},
};
}
configureStore(options) {
// 設置默認的middleware
const curriedGetDefaultMiddleware = curryGetDefaultMiddleware()
const {
reducer = undefined,
middleware = curriedGetDefaultMiddleware(),
devTools = true,
preloadedState = undefined,
enhancers = undefined,
} = options || {}
let rootReducer
//如果 reducer 是一個函數,直接使用該函數。
//如果是物件,則使用 combineReducers 將多個 reducer 組合起來。
if (typeof reducer === 'function') {
rootReducer = reducer
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer)
}
// 設定 middleware
let finalMiddleware = middleware
if (typeof finalMiddleware === 'function') {
finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
}
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
let finalCompose = compose
if (devTools) {
// DevTools 相關設定
}
// 設定 enhancers 來增加 store
let storeEnhancers = [middlewareEnhancer]
if (Array.isArray(enhancers)) {
storeEnhancers = [middlewareEnhancer, ...enhancers]
} else if (typeof enhancers === 'function') {
storeEnhancers = enhancers(storeEnhancers)
}
const composedEnhancer = finalCompose(...storeEnhancers)
// 將參數傳入,使用 redux 的 createStore 建立最終的 store
return createStore(rootReducer, preloadedState, composedEnhancer)
}
參考資料:
https://redux-toolkit.js.org/usage/usage-guide
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
https://redux-toolkit.js.org/usage/immer-reducers
https://github.com/reduxjs/redux-toolkit/tree/fc5cf086c74cd4a5a2dbe81070febe9c09e2841d/packages/toolkit/src
https://juejin.cn/post/7058923547464466440
https://immerjs.github.io/immer/api/