前一篇,我們已經完成了咖啡商品的購買功能,以及示範了 React 的 component 拆分方法,那麼這篇我們就接續完成另一項進貨的功能吧!
首先,我們先回到上一篇我們修改並拆分出來的 CoffeeBlock component 裏面做以下修改:
// 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,
money: coffeeOrderMoneyRef.current.value
}
dispatch(coffeeOrdered(sendData))
}
// 進貨送出
const doRestockCoffee = () => {
const sendData = {
qty: coffeeRestockQtyRef.current.value,
money: coffeeRestockMoneyRef.current.value
}
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
這時,有可能會遇到 type 不同的問題,但為了避免此情況發生影響功能,讓我們回到原本的 coffeeSlice.js 裏面針對我們送出的值做以下轉換修改:
// src/featuers/slices/coffeeSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
numOfCoffee: 20
}
const coffeeSlice = createSlice({
name: 'coffee',
initialState,
reducers: {
// 這裡避免傳入值為字串進而影響功能,所以加了 parseInt
coffeeOrdered: (state, action) => {
state.numOfCoffee = state.numOfCoffee - parseInt(action.payload.qty)
return state;
},
coffeeRestocked: (state, action) => {
state.numOfCoffee = state.numOfCoffee + parseInt(action.payload.qty)
return state;
},
},
})
// 方便辨識的處理
export const selectCoffee = (state) => state.coffee;
export const { coffeeOrdered, coffeeRestocked } = coffeeSlice.actions
export default coffeeSlice.reducer
這也是為何 TypeScript 會如此受歡迎的原因,在 js 的環境開發常常就會因為這些型別造成很多小 bug,因為在定義的時候通常不會硬性要求去限定型別,但這篇的目的是希望新手也能跟著做,所以大家只要注意這邊的地方要做以上處理即可。
至此,運行上應該就沒有問題了,那麼畫面的話我們還缺少了咖啡豆、蛋糕的部分,接著處理吧!
一樣從 slice 開始搬遷,於 src/features/slices 的路徑下新增 coffeeBeanSlice.js,完成後就開始搬遷如以下:
// src/features/slices/coffeeBeanSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
numOfCoffeeBean: 20
}
const coffeeBeanSlice = createSlice({
name: 'coffeeBean',
initialState,
reducers: {
// 一樣是為了避棉型別造成的問題
coffeeBeanOrdered: (state, action) => {
state.numOfCoffeeBean = state.numOfCoffeeBean - parseInt(action.payload.qty)
return state;
},
coffeeBeanRestocked: (state, action) => {
state.numOfCoffeeBean = state.numOfCoffeeBean + parseInt(action.payload.qty)
return state;
},
}
})
// 方便辨識的處理
export const selectCoffeeBean = (state) => state.coffeeBean;
export const { coffeeBeanOrdered, coffeeBeanRestocked } = coffeeBeanSlice.actions
export default coffeeBeanSlice.reducer
完成後不要忘記要調整 store 將剛才的 slice 給加進去:
// src/featuers/store.js
import { configureStore } from '@reduxjs/toolkit'
import coffeeBeanSlice from './slices/coffeeBeanSlice'
import coffeeSlice from './slices/coffeeSlice'
const store = configureStore({
reducer: {
coffee: coffeeSlice,
coffeeBean: coffeeBeanSlice
}
})
export default store
與之前的 coffeeBlock 相同的概念,我們直接於 components 下新增一個 coffeeBeanBlock.jsx 的檔案,用來製作新的 component,如以下:
// src/components/CoffeeBeanBlock.jsx
import React, { useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { coffeeBeanOrdered, coffeeBeanRestocked, selectCoffeeBean } from '../features/slices/coffeeBeanSlice'
const CoffeeBeanBlock = () => {
const coffeeBean = useSelector(selectCoffeeBean)
const dispatch = useDispatch()
// 購買
const coffeeBeanOrderQtyRef = useRef(null)
const coffeeBeanOrderMoneyRef = useRef(null)
// 進貨
const coffeeBeanRestockQtyRef = useRef(null)
const coffeeBeanRestockMoneyRef = useRef(null)
// 購買送出
const doOrderCoffeeBean = () => {
const sendData = {
qty: coffeeBeanOrderQtyRef.current.value,
money: coffeeBeanOrderMoneyRef.current.value
}
dispatch(coffeeBeanOrdered(sendData))
}
// 進貨送出
const doRestockCoffeeBean = () => {
const sendData = {
qty: coffeeBeanRestockQtyRef.current.value,
money: coffeeBeanRestockMoneyRef.current.value
}
dispatch(coffeeBeanRestocked(sendData))
}
console.log('咖啡豆', coffeeBean);
return (
<div>
<h4>咖啡豆存量 {coffeeBean.numOfCoffeeBean}</h4>
<fieldset>
<legend>咖啡豆購買</legend>
<div>
<label htmlFor="coffeeBeanOrderQty">數量</label>
<input id="coffeeBeanOrderQty" type="number" ref={coffeeBeanOrderQtyRef} />
</div>
<div>
<label htmlFor="coffeeBeanOrderMoney">價錢</label>
<input id="coffeeBeanOrderMoney" type="number" ref={coffeeBeanOrderMoneyRef} />
</div>
<button onClick={doOrderCoffeeBean}>
購買
</button>
</fieldset>
<fieldset>
<legend>咖啡豆進貨</legend>
<div>
<label htmlFor="coffeeBeanRestockQty">數量</label>
<input id="coffeeBeanRestockQty" type="number" ref={coffeeBeanRestockQtyRef} />
</div>
<div>
<label htmlFor="coffeeBeanRestockMoney">價錢</label>
<input id="coffeeBeanRestockMoney" type="number" ref={coffeeBeanRestockMoneyRef} />
</div>
<button onClick={doRestockCoffeeBean}>
進貨
</button>
</fieldset>
</div>
)
}
export default CoffeeBeanBlock
接著我們於 App 內引入我們剛才的段落,如下:
import CoffeeBeanBlock from "./components/CoffeeBeanBlock"
import CoffeeBlock from "./components/CoffeeBlock"
function App() {
return (
<div className="container">
<h1>Restaurant Record</h1>
<CoffeeBlock/>
<CoffeeBeanBlock/>
</div>
)
}
export default App
此時,於畫面上應該會有四個功能,分別為咖啡購買、咖啡進貨、咖啡豆購買、咖啡豆進貨,功能也是正常的,數量有填寫的話,送出也能使的庫存增減。
那麼今天的分享就到這裡,下一篇我們將蛋糕也搬錢過來吧!