使用 Redux
實現購物車案例
由于 redux 5.0 已經將 createStore 廢棄
,我們需要先將 @reduxjs/toolkit
安裝一下;
yarn add @reduxjs/toolkit// 或者
npm install @reduxjs/toolkit
使用 vite 創建 React
項目時候 配置路徑別名
:
// 第一種寫法
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
import { resolve } from 'path';
export default defineConfig({plugins: [react()],
...resolve: {alias: {'@': path.resolve(__dirname, './src') // 例如,設置一個別名路徑 @ 指向 src 目錄}}
...
})
// 第二種寫法
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';
import { resolve } from 'path';
const projectRoot = resolve(__dirname); // 獲取項目根目錄的絕對路徑
const srcPath = resolve(projectRoot, 'src'); // 獲取 src 目錄的絕對路徑
export default defineConfig({plugins: [react()],
...resolve: {alias: {'@': srcPath, // 例如,設置一個別名路徑 @ 指向 src 目錄}}
...
})
1、創建 購物車 store
分別新建
action/carAction.js 文件
reducer/carReducer.js 文件
1.1、添加action
常量類型
// store/action/carAction.js 文件
// action type 常量
const ADD_CART = 'ADD_CART' // 新增商品
const REMOVE_CART = 'REMOVE_CART' // 刪除商品
const ADD_NUM_CART = 'ADD_NUM_CART' // 增加數量
const REDUCE_NUM_CART = 'REDUCE_NUM_CART' // 減少數量
const EDIT_NUM_CART = 'REDUCE_NUM_CART' // 直接修改數量
const CHECKED_CART = 'CHECKED_CART' // 選中要結算的單據
1.2、添加 修改 state 的 action
方法
每個 action 中 必須包含一個 type
屬性的 常量,返回一個
對象,其余參數可以自行定義
拋出需要使用的方法
// store/action/carAction.js 文件
const addCart = (list, order={name: `商品${list.length + 1}`,id: `sss_${list.length + 1}` ,num: 1,price: 12.00,totalPrice: 12.00,
}) => ({type: ADD_CART,payload: {order: order,list: list}
})const removeCart = (id) => ({type: REMOVE_CART,payload: id
})
const addNumCart = (id, num) => ({type: ADD_NUM_CART,payload: {id, num}
})
const reduceNumCart = (id, num) => ({type: REDUCE_NUM_CART,payload: {id, num}
})
const editNumCart = (id, num) => ({type: EDIT_NUM_CART,payload: {id, num}
})
const checkedCart = (id) => ({type: CHECKED_CART,payload: {id}
})export {addCart,removeCart,addNumCart,reduceNumCart,editNumCart,checkedCart
}
1.3、添加 購物車 reducer
方法
自定義的 reducer
中接收兩個參數
state
: 當前的數據狀態
action
: 使用dispatch() 觸發的 action對象
// store/reducer/carReducer.js 文件
// 如果有初始值,我們可以這樣定義初始值
const initState = {list:[]
}// 購物的 reducer 根據action.type 類型進行業務邏輯處理
const carReducer = (state=initState, action) => {console.log('==carReducer=', state, action)let newLists = []switch (action.type) {case 'ADD_CART':return {list: [action.payload.order, ...state.list]}case 'REMOVE_CART':return {list: state.list.filter(itm => itm.id !== action.payload.id)}case 'ADD_NUM_CART':// 不可以直接修改 state.list 中的數據,這里的數據是只讀的state.list.map(itm => {if (itm.id === action.payload.id) {newLists.push({...itm,num: itm.num + 1,totalPrice: (itm.num + 1) * itm.price})} else {newLists.push({...itm})}})return {list: [...newLists]}case 'REDUCE_NUM_CART':state.list.map(itm => {if (itm.id === action.payload.id) {newLists.push({...itm,num: (itm.num > 0 ? itm.num - 1 : 0),totalPrice: (itm.num > 0 ? itm.num - 1 : 0) * itm.price})} else {newLists.push({...itm})}})return {list: [...newLists]}case 'EDIT_NUM_CART':// 直接修改 數量state.list.map(itm => {if (itm.id === action.payload.id) {newLists.push({...itm,num: action.payload.num,totalPrice: action.payload.num * itm.price})} else {newLists.push({...itm})}})return {list: [...newLists]}case 'CHECKED_CART':// 選中state.list.map(itm => {if (itm.id === action.payload.id) {newLists.push({...itm,isChecked: !itm?.isChecked})} else {newLists.push({...itm})}})return {list: [...newLists]}default :return {list: state.list}}
}export {carReducer
}
1.4、拋出 store 實例
當有多個 reducer
時,我們需要使用 combineReducers
將所有reducer 合并
// import { createStore } from 'redux';
// createStore 這種方案 在 5.0中已經棄用
import { configureStore } from '@reduxjs/toolkit'
import { combineReducers } from 'redux';
import { textReducer } from './reducer'
import { carReducer } from './reducer/carReducer.js'
const rootReducer = combineReducers({textReducer: textReducer,carReducer: carReducer,});
const store = configureStore({reducer: rootReducer
})export default store
2、使用觸發 購物車 store 數據更新
2.1、引入需要使用的 Action
方法
import { addCart,removeCart,addNumCart,reduceNumCart,editNumCart,checkedCart
} from '@/store/action/carAction.js'
2.2、獲取 useDispatch的dispatch 和 useSelector 中的 reducer
import { useDispatch, useSelector } from 'react-redux'
// useDispatch Hook 觸發 action 中方法
const dispatch = useDispatch()
// useSelector 獲取最新的 state 中購物車數據
const selector = useSelector(state => {console.log('=---selector-', state)return state.carReducer.list
})
2.3、完整案例代碼
import React, {useState, useEffect, useId} from 'react'
import './index.scss'
import { useDispatch, useSelector } from 'react-redux'
import { addCart,removeCart,addNumCart,reduceNumCart,editNumCart,checkedCart
} from '@/store/action/carAction.js'
export default function ShoppingCar() {const dispatch = useDispatch()const selector = useSelector(state => {console.log('=---selector-', state)return state.carReducer.list})const [totalNum, setTotalNum] = useState(0)const [totalPerice, setTotalPerice] = useState(0)const [list, setList] = useState([])const handleChangeNum = (type, id, num) => {if(type === 'ADD') {// 使用 dispatch 調用 action 中的 addNumCart 方法進行累加dispatch(addNumCart(id, num))} else{dispatch(reduceNumCart(id, num))}}const handleChangeCheckbox = (e, id) =>{// 使用 dispatch 調用 action 中的 checkedCart 獲取選中 反選操作dispatch(checkedCart(id))}const handleAdd = () => {// 新增商品dispatch(addCart([...selector]))}useEffect(() => {let isSelectedLists = []selector.map(itm => {if (itm.isChecked) {isSelectedLists.push(itm)}})console.log('=isSelectedLists==', isSelectedLists)const curNum = isSelectedLists && isSelectedLists.length &&isSelectedLists.reduce((total, item) => total + item.num, 0) || 0const curTotal = isSelectedLists && isSelectedLists.length && isSelectedLists.reduce((total, item) => total + item.totalPrice, 0) || 0console.log('==curNum==', curNum)console.log('==curTotal==', curTotal)setTotalNum(curNum)setTotalPerice(curTotal)console.log('=000=selector=', selector)setList([...selector])}, [selector])return (<div className='list'>{list.map(itm => {return (<div className="li" key={itm.id}><div className='commodity'><input type="checkbox" name="" id="" value={itm.isChecked} onClick={(e) => handleChangeCheckbox(e, itm.id)}/><span>{itm.name}</span></div><div className="price">單價:{itm.price}</div><div className='num'><span className='handle-icon' onClick={() => handleChangeNum('ADD', itm.id, itm.num)}>+</span><span className='itm-num'>{itm.num}</span><span className='handle-icon' onClick={() => handleChangeNum('REDUCE', itm.id, itm.num)}>-</span></div><div className='total'>總價:{itm.totalPrice}</div></div>)})}<div className='total'><span className='total-num'>共計:{totalNum}件</span><span className='total-price'>合計:{totalPerice}元</span></div><button className="btn" onClick={handleAdd}>增加商品</button></div>)
}
3、總結
1、使用 redux 方便在 reducer 中集中式管理業務代碼,提升代碼的維護性;
2、使用 store 統一管理 購物車的狀態,方便代碼進行復用,只需要傳入對應參數即可;
3、如果是簡單的邏輯,使用redux 進行狀態管理,會增加代碼的負責性,不如 直接使用 React 中自帶的 HOOKS 進行實現;
4、多頁面共享數據狀態,業務邏輯復雜的,使用 redux 更方便一些;