iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
自我挑戰組

30天深入淺出Redux系列 第 22

Redux 深入淺出 - [ Day 22 ] React Redux 資金功能設定

  • 分享至 

  • xImage
  •  

前面的章節我們完成了完整的商品進貨購買功能,是時候算個帳了!

https://ithelp.ithome.com.tw/upload/images/20220930/20129020HF31OtQLSZ.png

今天就是要把錢的部分給加進來,我們可以從先前的 Redux Toolkit 的專案內搬移 assetsSlice 進來,所以我們一樣於 src/features/slices 路徑下新增一個 assetsSlice.js 的檔案,將原本的內容做以下修改:

// src/features/slices/assetsSlice.js
import { createSlice } from "@reduxjs/toolkit";
import { cakeOrdered, cakeRestocked } from "./cakeSlice";
import { coffeeBeanOrdered, coffeeBeanRestocked } from "./coffeeBeanSlice";
import { coffeeOrdered, coffeeRestocked } from "./coffeeSlice";

const initialState = {
  money: 1000,
}

const assetsSlice = createSlice({
  name: 'assets',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
    .addCase(coffeeOrdered, (state, action) => {
      state.money = state.money + parseInt(action.payload.money)
      return state;
    })
    .addCase(coffeeRestocked, (state, action) => {
      state.money = state.money - parseInt(action.payload.money)
      return state;
    })
    .addCase(coffeeBeanOrdered, (state, action) => {
      state.money = state.money + parseInt(action.payload.money)
      return state;
    })
    .addCase(coffeeBeanRestocked, (state, action) => {
      state.money = state.money - parseInt(action.payload.money)
      return state;
    })
    .addCase(cakeOrdered, (state, action) => {
      state.money = state.money + parseInt(action.payload.money)
      return state;
    })
    .addCase(cakeRestocked, (state, action) => {
      state.money = state.money - parseInt(action.payload.money)
      return state;
    })
  }
})

// 方便辨識的處理
export const selectAssets = (state) => state.assets;

export default assetsSlice.reducer

完成後不要忘記調整 store,如下:

// src/features/store.js
import { configureStore } from '@reduxjs/toolkit'
import assetsSlice from './slices/assetsSlice'
import cakeSlice from './slices/cakeSlice'
import coffeeBeanSlice from './slices/coffeeBeanSlice'
import coffeeSlice from './slices/coffeeSlice'

const store = configureStore({
  reducer: {
    coffee: coffeeSlice,
    coffeeBean: coffeeBeanSlice,
    cake: cakeSlice,
    assets: assetsSlice
  }
})

export default store

到這裡基本的功能已經完成了,由於資金的部分都是依附在其他動作上的連動,所以畫面上基本沒有什麼需要做到的調整,但為了整齊我還是會在 components 裏面新增一個 component 用來單純顯示目前的資金狀況:

// src/components/AssetsBlock.jsx
import React from 'react'
import { useSelector } from 'react-redux'
import { selectAssets } from '../features/slices/assetsSlice'

const AssetsBlock = () => {
  const assets = useSelector(selectAssets)
  return (
    <div>
      <h4>資金 {assets.money}</h4>
    </div>
  )
}

export default AssetsBlock

然後將剛剛做好的 component 引入 App 顯示:

// src/App.jsx
import AssetsBlock from "./components/AssetsBlock"
import CakeBlock from "./components/CakeBlock"
import CoffeeBeanBlock from "./components/CoffeeBeanBlock"
import CoffeeBlock from "./components/CoffeeBlock"

function App() {

  return (
    <div className="container">
      <h1>Restaurant Record</h1>
      <AssetsBlock/>
      <CoffeeBlock/>
      <CoffeeBeanBlock/>
      <CakeBlock/>
    </div>
  )
}

export default App

此時可以測試看看資金的數值是否有根據其他的動作而改變,那麼接著我們要阻止一件事是如果欄位為空時要補數字0,不然會造成 NaN 的囧況,所以我們回到每個 component 裏面修正一下送出的 function,讓 null 轉成 0,我就以 CoffeeBlock 為例修正:

// src/components/CoffeeBlock.jsx
import React, { useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { coffeeOrdered, coffeeRestocked, selectCoffee } from '../features/slices/coffeeSlice'

const CoffeeBlock = () => {
  const coffee = useSelector(selectCoffee)
  const dispatch = useDispatch()
  // 這裡採 react 簡單的 useRef 來處理
  // 購買
  const coffeeOrderQtyRef = useRef(null)
  const coffeeOrderMoneyRef = useRef(null)
  // 進貨
  const coffeeRestockQtyRef = useRef(null)
  const coffeeRestockMoneyRef = useRef(null)
  // 購買送出
  const doOrderCoffee = () => {
    const sendData = {
      qty: coffeeOrderQtyRef.current.value || 0,
      money: coffeeOrderMoneyRef.current.value || 0
    }
    dispatch(coffeeOrdered(sendData))
  }
  // 進貨送出
  const doRestockCoffee = () => {

    const sendData = {
      qty: coffeeRestockQtyRef.current.value || 0,
      money: coffeeRestockMoneyRef.current.value || 0
    }
    dispatch(coffeeRestocked(sendData))
  }
  console.log('coffee', coffee);
  return (
    <div>
      <h4>咖啡存量 {coffee.numOfCoffee}</h4>
      <fieldset>
        <legend>咖啡購買</legend>
        <div>
          <label htmlFor="coffeeOrderQty">數量</label>
          <input id="coffeeOrderQty" type="number" ref={coffeeOrderQtyRef} />
        </div>
        <div>
          <label htmlFor="coffeeOrderMoney">價錢</label>
          <input id="coffeeOrderMoney" type="number" ref={coffeeOrderMoneyRef} />
        </div>
        <button onClick={doOrderCoffee}>
          購買
        </button>
      </fieldset>
      <fieldset>
        <legend>咖啡進貨</legend>
        <div>
          <label htmlFor="coffeeRestockQty">數量</label>
          <input id="coffeeRestockQty" type="number" ref={coffeeRestockQtyRef} />
        </div>
        <div>
          <label htmlFor="coffeeRestockMoney">價錢</label>
          <input id="coffeeRestockMoney" type="number" ref={coffeeRestockMoneyRef} />
        </div>
        <button onClick={doRestockCoffee}>
          進貨
        </button>
      </fieldset>
    </div>
  )
}

export default CoffeeBlock

其餘的 component 也做一樣的修正後應該能解決 NaN 造成的小 bug。

那麼,今天的內容就到這,下一篇我們來處理 pokemon 的搬移。


上一篇
Redux 深入淺出 - [ Day 21 ] React Redux 商品功能設定 - (蛋糕)
下一篇
Redux 深入淺出 - [ Day 23 ] React Redux 非同步處理
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言