iT邦幫忙

2022 iThome 鐵人賽

DAY 19
0
自我挑戰組

30天深入淺出Redux系列 第 19

Redux 深入淺出 - [ Day 19 ] React Redux setup

  • 分享至 

  • xImage
  •  

前篇我們已經完成了基本的 Store setting,並透過 React Redux 的 provider 提供所有 component 來使用,其用意與 React 本身的 context provider 相同,那麼今天我們就將其他對應的 Slice 給搬遷過來。

https://ithelp.ithome.com.tw/upload/images/20220923/201290204vRwltAER3.png

那麼,我們就在 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 的進貨動作。


上一篇
Redux 深入淺出 - [ Day 18 ] React Redux 簡介
下一篇
Redux 深入淺出 - [ Day 20 ] React Redux 商品功能設定-(咖啡&咖啡豆)
系列文
30天深入淺出Redux31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言