前一篇我們已經完成整併了我們的每項商品,但是資金的部分我們還沒處理,像原本 redux 的做法就是靠 action type 來做標記,相同標記取不同值做不同操作,這樣的情況很容易搞混,所以 redux-toolkit 提供了單一 slice 的概念,但想要影響其他的 state 的時候該怎麼辦?
extraReducers 允許 createSlice 響應除了它生成的 action type 之外的其他 action type,這樣講或許很難懂,簡單來說就是必須靠這個方法來讓其他 slice state 也能連動。—官網連結
我們目前的 action 只有單純接一個數字參數作為 payload,但這樣與先前的 function 是不相同的,所以我們前面的 action function 裏面應該要加設 assets & qty 兩個 key,那在此之前,我們還是要先處理 assets 的 state 進入我們的 store.js 中,新增 assetsSlice 檔案於 features/slices 路徑下:
// features/slices/assetsSlice.js
const { createSlice } = require("@reduxjs/toolkit");
// only assetsReducer
const initialState = {
money: 1000,
}
const assetsSlice = createSlice({
name: 'assets',
initialState,
reducers: {}
})
module.exports = assetsSlice.reducer;
module.exports.cakeActions = assetsSlice.actions;
於 store.js 加入剛剛的 slice,如下:
// features/store.js
const configureStore = require('@reduxjs/toolkit').configureStore;
const coffeeReducer = require('./slices/coffeeSlice');
const coffeeBeanReducer = require('./slices/coffeeBeanSlice');
const cakeReducer = require('./slices/cakeSlice');
const assetsReducer = require('./slices/assetsSlice');
const store = configureStore({
reducer: {
coffee: coffeeReducer,
coffeeBean: coffeeBeanReducer,
cake: cakeReducer,
assets: assetsReducer
}
})
module.exports = { store }
可以測試看看initial state 應該會拿到 assets 的 1000,如下:
Initial State {
coffee: { numOfCoffee: 20 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 19 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 15 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 18 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 23 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 23 },
cake: { numOfCake: 17 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 23 },
cake: { numOfCake: 23 },
assets: { money: 1000 }
}
還記得我們本來帶的 function 參數嗎?為了要將錢的值也傳入,我們做以下簡單的處理:
// ./index.js
// 省略...
// 以 coffee 為範例,相同的 function 將改成以下的方式處理,並將其他的指令先碼掉
store.dispatch(coffeeActions.coffeeOrdered({qty: 1, money: 10}));
store.dispatch(coffeeActions.coffeeOrdered({qty: 4, money: 40}));
store.dispatch(coffeeActions.coffeeRestocked({qty: 10, money: 20}));
接著,我們回到 coffeeSlice 裏面作些微的修正:
const createSlice = require('@reduxjs/toolkit').createSlice;
// 同 only 的 coffeeReducer initial state
const initialState = {
numOfCoffee: 20
}
const coffeeSlice = createSlice({
name: 'coffee',
initialState,
reducers: {
// 這裡固定會有 sate & action 兩參數,直接對應於上面的 state
// 是 qty,我加了 qty
coffeeOrdered: (state, action) => {
state.numOfCoffee = state.numOfCoffee - action.payload.qty
return state;
},
coffeeRestocked: (state, action) => {
state.numOfCoffee = state.numOfCoffee + action.payload.qty
return state;
},
}
})
// 這邊就要注意 default 的情況下輸出 reducer 對應的 action function 會從 coffeeActions 引入
module.exports = coffeeSlice.reducer;
module.exports.coffeeActions = coffeeSlice.actions;
同理我們希望顧客買咖啡的時候將資金加入,於老闆補貨的同時扣除費用,那麼我們再回到 assetsSlice,透過 extraReducer 處理,就會如下:
// features/slices/assetsSlice
const { createSlice } = require("@reduxjs/toolkit");
const { coffeeOrdered, coffeeRestocked } = require('./coffeeSlice').coffeeActions;
// only assetsReducer
const initialState = {
money: 1000,
}
const assetsSlice = createSlice({
name: 'assets',
initialState,
reducers: {},
// 這個是讓其他的slice reducer function來影響這裡的state,以下為兩種不同的寫法。
// extraReducers: {
// ['coffee/coffeeOrdered']: (state, action) => {
// state.money = state.money + action.payload.money
// return state;
// },
// ['coffee/coffeeRestocked']: (state, action) => {
// state.money = state.money - action.payload.money
// return state;
// },
// }
extraReducers: (builder) => {
builder
.addCase(coffeeOrdered, (state, action) => {
state.money = state.money + action.payload.money
return state;
})
.addCase(coffeeRestocked, (state, action) => {
state.money = state.money - action.payload.money
return state;
})
}
})
module.exports = assetsSlice.reducer;
module.exports.cakeActions = assetsSlice.actions;
這裡有兩種方式可以編寫 extraReducers,我這邊也提供給大家參考,其實挑自己習慣的即可,認真問我的話,我個人的話也是偏向 builder 派的,但這裡真的沒有對錯,所以不要被搞錯方向了,完成以上修改我們可以來測試看看資金是否也有發生變化。
於 ./index.js 作出以下修改:
// ./index.js
const { store } = require('./features/store');
const coffeeActions = require('./features/slices/coffeeSlice').coffeeActions;
const coffeeBeanActions = require('./features/slices/coffeeBeanSlice').coffeeBeanActions;
const cakeActions = require('./features/slices/cakeSlice').cakeActions;
console.log('Initial State', store.getState());
const unsubscribe = store.subscribe(() => console.log('更新', store.getState()));
store.dispatch(coffeeActions.coffeeOrdered({qty: 1, money: 10}));
store.dispatch(coffeeActions.coffeeOrdered({qty: 4, money: 40}));
store.dispatch(coffeeActions.coffeeRestocked({qty: 10, money: 20}));
// store.dispatch(coffeeBeanActions.coffeeBeanOrdered(2));
// store.dispatch(coffeeBeanActions.coffeeBeanRestocked(5));
// store.dispatch(cakeActions.cakeOrdered(3));
// store.dispatch(cakeActions.cakeRestocked(6));
unsubscribe();
終端機回傳訊息:
Initial State {
coffee: { numOfCoffee: 20 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1000 }
}
更新 {
coffee: { numOfCoffee: 19 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1010 }
}
更新 {
coffee: { numOfCoffee: 15 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1050 }
}
更新 {
coffee: { numOfCoffee: 25 },
coffeeBean: { numOfCoffeeBean: 20 },
cake: { numOfCake: 20 },
assets: { money: 1030 }
}
以上就是透過 extraReducer 去影響 slice 的 state 的方法,那麼接下來的練習就會非常相似,下一篇我們來把其他兩項商品也將上這樣的操作。