Redux API 重點 - 分別是 Store、Actions、Reducer
前篇介紹了 Store 及 Actions,這篇就來詳細介紹 Reducer
Reducer 是用來應對改變 State 的函式,它是一個 Pure Function
,接受兩個參數,先前的 State 和 Action。
當 Store 收到 Action 通知後,一定要傳回一個「全新的State」,這樣 React Component 才能發生變化。
(previousState, action) => newState
Store 接收到 Action 傳來的資料,然後根據邏輯計算資料,這個過程就稱為 Reducer。這個 Function 之所以被稱為 Reducer,是因為它就像是 Array.prototype.reduce(reducer, ?initialValue) 的 Pure Function 類型。
Pure Function
- 將相同的輸入丟入,永遠都會回傳相同的輸出,並且不對任何該函數以外的任何作用域產生影響。
接下來我們可以假定 TODO MVC 為想要設計的應用程式,來思考 Reducer 要怎麼做設計
觀察 Todo MVC 的畫面,我們可以發現整個應用程式需要二個 State 如下,分別是 filter 字串,及 todos 陣列清單。
{
filter: 'ALL', // ACTIVE, COMPLETED
todos: [
{
text: '測試A',
completed: true,
},
{
text: '測試B',
completed: false
}
]
}
Reducer 是一個 Pure function,它接收先前的 State 和一個 action,然後回傳一個「全新的State」。
(previousState, action) => newState
首先,先定義一個 Initial State,如果傳入的 State 未定義,就設定Initial State給它,如果不是定義好的 Actions,都回傳先前的 State。
const initialState = {
filter: 'ALL',
todos: []
};
const todosReducer = (state = initialState, action) => {
switch (action.type) {
...
default:
return state
}
}
觀察 Todo MVC 的畫面操作,我們可以發現至少需要制定以下幾個 Action,且思考要回傳的全新的 State 的結構。
const todosReducer = (state = initialState, action) =>
switch (action.type) {
case SET_FILTER: // 依照 Filter 去顯示 TODO 項目
return Object.assign({}, state, {
filter: action.filter
});
case ADD_TODO: // 新增一個 TODO 項目
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
});
case TOGGLE_TODO: // 把一個 TODO 項目標示是否完成
return Object.assign({}, state, {
todos: state.todos.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
});
default: // 未知的 Action
return state;
}
}
使用 Object.assign 的方式,可確保永遠都是全新的 State,如果覺得這樣的操作很麻煩,也可以考慮引用第三方的函式庫,像是 Immutable.js 或是 Immer.js 來幫助開發。
仔細觀察 State,可以發現 todos 和 filter 的更新似乎是完全獨立的。操作 todos 及 filter 的 Action 對應,也可以拆分成二塊,所以我們可以拆分前面的 Reducer Function,變成二個 Reducer Function,且各自更新它們對應的 State。
const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
];
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
});
}
return todo;
});
default:
return state;
}
};
const filterReducer = (state = 'ALL', action) => {
switch (action.type) {
case SET_FILTER:
return action.filter;
default:
return state;
}
};
請記住這些 Reducer 每一個都管理它所擁有的全域 State 一部分。每個 Reducer 的 State 參數都不一樣,並對應到它管理的部分 State。
隨著應用程式大小增長,我們可以把 Reducers 拆分成個別的檔案並讓它們完全獨立並管理不同的資料領域。
拆分成各別區域狀態的 State,可稱之為 Slice (切片),所以對應的 Reducer 就稱為 Slice Reducer。未來介紹 Redux Toolkit 時會再出現一次此名詞。
Redux 提供一個 utility 叫做 combineReducers()
,可以用來組合多個 Slice Reducer。
把組合好的 reducers 傳入到 createStore 函式中,就完成 Reducers 與 Store 的連結。
import {createStore, combineReducers} from 'redux';
const reducers = combineReducers(todosReducer, filterReducer);
cosnt store = createStore(reducers);
export default store;
在前面二篇,我們把 Redux API 的重點 Store、Actions、Reducer 都介紹完成了,也用範例來示範了建置的方式,接下來就要示範 React Compoent 要如何去使用 Redux 來完成狀態管理。
https://www.freecodecamp.org/news/what-is-redux-store-actions-reducers-explained/
https://www.laitimes.com/article/1od0a_1u849.html
https://chentsulin.github.io/redux/index.html
https://pjchender.dev/webdev/note-without-redux/
https://ithelp.ithome.com.tw/articles/10219962