前篇我們已經完成了基本的 Store setting,並透過 React Redux 的 provider 提供所有 component 來使用,其用意與 React 本身的 context provider 相同,那麼今天我們就將其他對應的 Slice 給搬遷過來。
那麼,我們就在 features 資料夾下新增 coffeeSlice,並將原本的 coffeeSlice 移轉過來,並調整引入與輸出的部分:
// src/features/slices/coffeeSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
numOfCoffee: 20
}
const coffeeSlice = createSlice({
name: 'coffee',
initialState,
reducers: {
// 這裡固定會有 sate & action 兩參數,直接對應於上面的 state
coffeeOrdered: (state, action) => {
state.numOfCoffee = state.numOfCoffee - action.payload.qty
return state;
},
coffeeRestocked: (state, action) => {
state.numOfCoffee = state.numOfCoffee + action.payload.qty
return state;
},
},
})
export const { coffeeOrdered, coffeeRestocked } = coffeeSlice.actions
export default coffeeSlice.reducer
完成後一樣至 store 做引入剛才的 coffeeSlice,如下:
// src/featuers/store.js
import { configureStore } from '@reduxjs/toolkit'
import coffeeSlice from './slices/coffeeSlice'
const store = configureStore({
reducer: {
coffee: coffeeSlice
}
})
export default store
回到 App.jsx 內做調整引入剛剛作成的 initialState 的資料,此時我們需要引入一個 useSelector 的 function 方便讀取 store 裏面的資料,如下:
// src/App.jsx
import { useSelector } from "react-redux"
function App() {
// 這裡建議採用callback的方式去讀取相應的state,會比較不影響效能
const coffee = useSelector(state => state.coffee)
console.log('coffee',coffee);
return (
<div className="container">
<h1>Hello World!</h1>
</div>
)
}
export default App
這時候畫面上 devtool 的 console 面板應該可以看到我們的 initialState 的資料,那為了方便辨識我會做以下處理,我怕 naming 的問題無法簡單的意會其中的意義,首先我們先回到原先的 coffeeSlice 檔案內做以下的封包處理:
// src/features/slices/coffeeSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
numOfCoffee: 20
}
const coffeeSlice = createSlice({
name: 'coffee',
initialState,
reducers: {
// 這裡固定會有 sate & action 兩參數,直接對應於上面的 state
coffeeOrdered: (state, action) => {
state.numOfCoffee = state.numOfCoffee - action.payload.qty
return state;
},
coffeeRestocked: (state, action) => {
state.numOfCoffee = state.numOfCoffee + action.payload.qty
return state;
},
},
})
// 方便辨識的處理
export const selectCoffee = (state) => state.coffee;
export const { coffeeOrdered, coffeeRestocked } = coffeeSlice.actions
export default coffeeSlice.reducer
此時我們回到 App.jsx 裏面做以下處理:
// src/App.jsx
import { useSelector } from "react-redux"
import { selectCoffee } from "./features/slices/coffeeSlice";
function App() {
// 這裡建議採用callback的方式去讀取相應的state,會比較不影響效能
// const coffee = useSelector(state => state.coffee)
const coffee = useSelector(selectCoffee)
console.log('coffee',coffee);
return (
<div className="container">
<h1>Hello World!</h1>
</div>
)
}
export default App
此時,畫面的結果應該與之前是相同的,接下來我們試著建立一個簡單的畫面吧!
還記得怎麼呼叫原本的 action 嗎?透過 dispatch 去呼叫 slice 的 action,那們同理在 React 的環境下也有這麼一個 hook 能處理這部分的程序,就是 useDispatch,那我們在引用的同時也要注意我們定義的 function 仍然是原本的格式 { qty: number, money: number },所以我們簡單的用兩個 input 來呈現訂單的功能,如下:
// src/App.jsx
import { useRef } from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux"
import { coffeeOrdered, selectCoffee } from "./features/slices/coffeeSlice";
function App() {
// 這裡建議採用callback的方式去讀取相應的state,會比較不影響效能
// const coffee = useSelector(state => state.coffee)
const coffee = useSelector(selectCoffee)
const dispatch = useDispatch()
// 這裡採 react 簡單的 useRef 來處理
const coffeeOrderQtyRef = useRef(null)
const coffeeOrderMoneyRef = useRef(null)
const doOrderCoffee = () => {
const sendData = {
qty: coffeeOrderQtyRef.current.value,
money: coffeeOrderMoneyRef.current.value
}
dispatch(coffeeOrdered(sendData))
}
console.log('coffee', coffee);
return (
<div className="container">
<h1>Restaurant Record</h1>
<div>
<h4>咖啡存量 {coffee.numOfCoffee}</h4>
<fieldset>
<legend>咖啡購買</legend>
<div>
<label htmlFor="coffeeQty">數量</label>
<input id="coffeeQty" type="number" ref={coffeeOrderQtyRef} />
</div>
<div>
<label htmlFor="coffeeMoney">價錢</label>
<input id="coffeeMoney" type="number" ref={coffeeOrderMoneyRef} />
</div>
<button onClick={doOrderCoffee}>
購買
</button>
</fieldset>
</div>
</div>
)
}
export default App
那麼這實在輸入完數量及金額按下購買按鈕時,會發現原本的 numOfCoffee 會因為你數量欄位帶入的數值而做減少,同理我們可以一樣將進貨的功能也一併寫入。當然可以直接在這裡接續寫入但請原諒我的強迫症,我實在不喜歡看到太多行明明可以拆出來卻不這樣處理的狀態,clean code 的意義對我而言重點在 keep clean,也希望剛進入 react 的新手們能持續保有這樣的好習慣。
那麼我們先來處理分檔,這邊我們先於 src 下新增 components 的資料夾,並於裏面新增一個 CoffeeBlock.jsx 的檔案,將剛剛的咖啡購買段落搬移過來,如下:
// src/components/CoffeeBlock.jsx
import React, { useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { coffeeOrdered, 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 doOrderCoffee = () => {
const sendData = {
qty: coffeeOrderQtyRef.current.value,
money: coffeeOrderMoneyRef.current.value
}
dispatch(coffeeOrdered(sendData))
}
console.log('coffee', coffee);
return (
<div>
<h4>咖啡存量 {coffee.numOfCoffee}</h4>
<fieldset>
<legend>咖啡購買</legend>
<div>
<label htmlFor="coffeeQty">數量</label>
<input id="coffeeQty" type="number" ref={coffeeOrderQtyRef} />
</div>
<div>
<label htmlFor="coffeeMoney">價錢</label>
<input id="coffeeMoney" type="number" ref={coffeeOrderMoneyRef} />
</div>
<button onClick={doOrderCoffee}>
購買
</button>
</fieldset>
</div>
)
}
export default CoffeeBlock
最後,不要忘記整理原本的 App.jsx,如下:
// src/App.jsx
import CoffeeBlock from "./components/CoffeeBlock"
function App() {
return (
<div className="container">
<h1>Restaurant Record</h1>
<CoffeeBlock/>
</div>
)
}
export default App
此時,畫面應該不會有變動。
那麼就完成 React component 的基本拆分了,下一篇我們來處理 coffee 的進貨動作。