iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
自我挑戰組

30天深入淺出Redux系列 第 14

Redux 深入淺出 - [ Day 14 ] Redux Toolkit ExtraReducer

  • 分享至 

  • xImage
  •  

前一篇我們已經完成整併了我們的每項商品,但是資金的部分我們還沒處理,像原本 redux 的做法就是靠 action type 來做標記,相同標記取不同值做不同操作,這樣的情況很容易搞混,所以 redux-toolkit 提供了單一 slice 的概念,但想要影響其他的 state 的時候該怎麼辦?

ExtraReducer

extraReducers 允許 createSlice 響應除了它生成的 action type 之外的其他 action type,這樣講或許很難懂,簡單來說就是必須靠這個方法來讓其他 slice state 也能連動。—官網連結
https://ithelp.ithome.com.tw/upload/images/20220921/20129020LLH7pUoCvb.png

調整

我們目前的 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 的方法,那麼接下來的練習就會非常相似,下一篇我們來把其他兩項商品也將上這樣的操作。


上一篇
Redux 深入淺出 - [ Day 13 ] Redux Toolkit Slice
下一篇
Redux 深入淺出 - [ Day 15 ] Redux Toolkit ExtraReducer 整理咖啡豆
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言