Reducers接收action和prev state,並回傳新的state,action只傳遞要改變的類型(type)。而一開始的state,我們使用ES6 Default parameters定義在reducer function的parameter預設值中。
而寫Reducer應該要特別注意“不可以”的有三件事:
setInterval
、fetch
api/AJAX,或是routing轉換等。Math.random()
或是Date.now()
。有注意到這三件事情都和我們先前提到 reducer is pure 有相關嗎!撰寫reducer的原則就是,傳給它的prev state、action和它回傳的next state,要是可以predictable的,所以這當中不能有隨機或是不可預期的code。
以下用filter的reducer當範例:
// filter預設初始值為 'SHOW_ALL'
function filter(state = 'SHOW_ALL', action) {
switch (action.type) {
// 根據傳入type,來處理next state並回傳
case 'SET_FILTER':
return action.filter;
// 預設回傳原本的state,任何未定義的action也傳回prev state
default:
return state;
}
}
上面示範的是改變string類型的state,如果是object和array的話,這邊必須特別小心,還記得reducer不可以有的第一件事情,就是「不可以改變它的參數」(prev state),所以object我們可以使用Object.assign
重新指定,array也可以使用Spread Operator。
以下用todos,新增task來示範修改array的方式:
// todos預設初始值為 空陣列
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TASK':
return [
...state,
{
task: action.task,
isCompleted: false
}
];
default:
return state;
}
}
再加上toggle task是否完成,示範修改object的方式:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TASK':
return [
...state,
{
task: action.task,
isCompleted: false
}
]);
case 'EDIT_TASK':
// 使用Spread Operator把要改變的array列舉出來
// 再用,Object.assign把要toggle的物件複製出來,並改變isCompleted
return [
...state.slice(0, action.idx),
Object.assign({}, state[action.idx], {
isCompleted: !state[action.idx].isCompleted
}),
...state.slice(action.idx + 1)
];
default:
return state;
}
}
每個reducer都是獨立處理的,也都會對應到它處理的state,所以通常會把不同reducer拆成不同檔案,避免檔案太長太複雜。在Redux中,我們使用combineReducers()
,產生一個function,可以透過這個function來處理每一個reducer,而我們必須傳給combineReducers()
每個state對應的reducer function,例如:combineReducers({ todos: todosReducer, filter: filterReducer })
,這表示我們的state tree會是{ todos, filter }
。
使用方式,假設我們把filter和todos拆成兩個filter.js、todos.js,再用index.js把兩個reducers整合在一起,資料夾架構如下:
└── reducers
├── index.js
├── filter.js
└── todos.js
首先要先安裝redux package:
npm install redux --save
todos.js和filter.js,要把function export出來,讓index.js可以import:
export default function todos(state = [], action) {
// ...
}
export default function filter(state = 'SHOW_ALL', action) {
// ...
}
reducers/index.js,使用combineReducers()
把所有reducers組合:
import { combineReducers } from 'redux';
import filter from './filter';
import todos from './todos';
const todoApp = combineReducers({
filter,
todos
});
export default todoApp;
這樣就完成了reducers的功能!目前已經建立reducers在原本的todos範例中,更新的檔案放在Git 上。
你好,
謝謝文章講解,想確認我的想法是不是對的
所以每個Reducer 都會依據Action去做出它們應該吐出來(return)的資料
並且將這些資料拿去給state tree。
設定state的方式就好像
todos:todosReducer
然後這個todos state就可以被存進去唯一的Store,
接著這些state就可以被當作全域state 然後被componets 使用?
不知道我這樣想是不是對的呢?
我覺得您講的很仔細,很棒。
你描述的過程沒錯喔!設定state的方式,就是透過combineReducers,然後今天會繼續講store的部分喔!
非常感謝你的鼓勵~~