使用 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) || 0
const curTotal = isSelectedLists && isSelectedLists.length && isSelectedLists.reduce((total, item) => total + item.totalPrice, 0) || 0
console.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 更方便一些;