React 之 Redux 第三十一节 useDispatch() 和 useSelector()使用以及详细案例

news2025/4/17 0:54:55

使用 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 更方便一些;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Llama 4全面评测:官方数据亮眼,社区测试显不足之处

引言 2025年4月&#xff0c;Meta正式发布了全新的Llama 4系列模型&#xff0c;这标志着Llama生态系统进入了一个全新的时代。Llama 4不仅是Meta首个原生多模态模型&#xff0c;还采用了混合专家(MoE)架构&#xff0c;并提供了前所未有的上下文长度支持。本文将详细介绍Llama 4…

【C++】函数直接返回bool值和返回bool变量差异

函数直接返回bool值和返回bool变量差异 背景 在工作中遇到一个比较诡异的问题&#xff0c;场景是给业务方提供的SDK有一个获取状态的函数GetStatus&#xff0c;函数的返回值类型是bool&#xff0c;在测试过程中发现&#xff0c;SDK返回的是false&#xff0c;但是业务方拿到的…

第1节:计算机视觉发展简史

计算机视觉与图像分类概述&#xff1a;计算机视觉发展简史 计算机视觉&#xff08;Computer Vision&#xff09;作为人工智能领域的重要分支&#xff0c;是一门研究如何使机器"看"的科学&#xff0c;更具体地说&#xff0c;是指用摄影机和计算机代替人眼对目标进行识…

英伟达Llama-3.1-Nemotron-Ultra-253B-v1语言模型论文快读:FFN Fusion

FFN Fusion: Rethinking Sequential Computation in Large Language Models 代表模型&#xff1a;Llama-3.1-Nemotron-Ultra-253B-v1 1. 摘要 本文介绍了一种名为 FFN Fusion 的架构优化技术&#xff0c;旨在通过识别和利用自然并行化机会来减少大型语言模型&#xff08;LLM…

云曦月末断网考核复现

Web 先看一个BUUCTF中的文件一个上传题 [BUUCTF] 2020新生赛 Upload 打开后是一个文件上传页面 随便上传一个txt一句话木马后出现js弹窗&#xff0c;提示只能上传图片格式文件 说明有前端验证。我的做法是把一句话改为.jpg格式&#xff0c; 然后上传 访问发现虽然上传成功了…

Flutter常用组件实践

Flutter常用组件实践 1、MaterialApp 和 Center(组件居中)2、Scaffold3、Container(容器)4、BoxDecoration(装饰器)5、Column(纵向布局)及Icon(图标)6、Column/Row(横向/横向布局)+CloseButton/BackButton/IconButton(简单按钮)7、Expanded和Flexible8、Stack和Po…

0.机器学习基础

0.人工智能概述&#xff1a; &#xff08;1&#xff09;必备三要素&#xff1a; 数据算法计算力 CPU、GPU、TPUGPU和CPU对比&#xff1a; GPU主要适合计算密集型任务&#xff1b;CPU主要适合I/O密集型任务&#xff1b; 【笔试问题】什么类型程序适合在GPU上运行&#xff1…

系统与网络安全------网络通信原理(4)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 网络层解析 IP 网络层概述 位于OSI模型第三层作用 定义网络设备的逻辑地址&#xff0c;俗称网络层地址&#xff08;如IP地址&#xff09; 在不同的网段之间选择最佳数据转发路径 协议 IP协议 IP数据包…

Java基础 4.12

1.方法的重载&#xff08;OverLoad&#xff09; 基本介绍 Java中允许同一个类&#xff0c;多个同名方法的存在&#xff0c;但要求形参列表不一致&#xff01; 如 System.out.println(); out是PrintStream类型 重载的好处 减轻了起名的麻烦减轻了记名的麻烦 2.重载的快速入…

XILINX DDR3专题---(1)IP核时钟框架介绍

1.什么是Reference Clock&#xff0c;这个时钟一定是200MHz吗&#xff1f; 2.为什么APP_DATA是128bit&#xff0c;怎么算出来的&#xff1f; 3.APP &#xff1a;MEM的比值一定是1:4吗&#xff1f; 4.NO BUFFER是什么意思&#xff1f; 5.什么情况下Reference Clock的时钟源可…

clickhouse注入手法总结

clickhouse 遇到一题clickhouse注入相关的&#xff0c;没有见过&#xff0c;于是来学习clickhouse的使用&#xff0c;并总结相关注入手法。 环境搭建 直接在docker运行 docker pull clickhouse/clickhouse-server docker run -d --name some-clickhouse-server --ulimit n…

React 组件样式

在这里插入图片描述 分为行内和css文件控制 行内 通过CSS中类名文件控制

利用 pyecharts 实现地图的数据可视化——第七次人口普查数据的2d、3d展示(关键词:2d 、3d 、map、 geo、涟漪点)

参考文档&#xff1a;链接: link_pyecharts 官方文档 1、map() 传入省份全称&#xff0c;date_pair 是列表套列表 [ [ ],[ ] … ] 2、geo() 传入省份简称&#xff0c;date_pair 是列表套元组 [ ( ),( ) … ] 1、准备数据 population_data&#xff1a;简称经纬度 population_da…

解决 Elasticsearch 分页查询性能瓶颈——从10分钟到秒级的优化实践

大家好&#xff0c;我是铭毅天下&#xff0c;一名专注于 Elasticsearch &#xff08;以下简称ES&#xff09;技术栈的技术爱好者。 今天我们来聊聊球友提出的一个实际问题&#xff1a; ES分页查询性能很差&#xff0c;使用from/size方式检索居然需要10分钟&#xff01; 这是一个…

记录IBM服务器检测到备份GPT损坏警告排查解决过程

服务器设备&#xff1a;IBM x3550 M4 Server IMM默认IP地址&#xff1a;192.168.70.125 用户名&#xff1a;USERID 密码&#xff1a;PASSW0RD&#xff08;注意是零0&#xff09; 操作系统&#xff1a;Windows Hyper-V Server 2016 IMM Web System Status Warning&#xff1…

毫米波测试套装速递!高效赋能5G/6G、新材料及智能超表面(RIS)研发

德思特&#xff08;Tesight&#xff09;作为全球领先的测试测量解决方案提供商&#xff0c;始终致力于为前沿技术研发提供高精度、高效率的测试工具。 针对毫米波技术在高频通信、智能超表面&#xff08;RIS&#xff09;、新材料等领域的快速应用需求&#xff0c;我们推出毫米…

Linux中卸载宝塔面板

输入命令 wget http://download.bt.cn/install/bt-uninstall.sh 执行脚本命令 sh bt-uninstall.sh 根据自己的情况选择1还是2 卸载完成校验 bt 这样我们的宝塔面板就卸载完了

无人机的振动与噪声控制技术!

一、振动控制技术要点 1. 振动源分析 气动振动&#xff1a;旋翼桨叶涡脱落&#xff08;如叶尖涡干涉&#xff09;、动态失速&#xff08;Dynamic Stall&#xff09;引发的周期性气动激振力&#xff08;频率与转速相关&#xff09;。 机械振动&#xff1a;电机偏心、传动轴不…

【蓝桥杯】第十六届蓝桥杯 JAVA B组记录

试题 A: 逃离高塔 很简单&#xff0c;签到题&#xff0c;但是需要注意精度&#xff0c;用int会有溢出风险 答案&#xff1a;202 package lanqiao.t1;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWrit…

OSPF的接口网络类型【复习篇】

OSPF在不同网络环境下默认的不同工作方式 [a3]display ospf interface g 0/0/0 # 查看ospf接口的网络类型网络类型OSPF接口的网络类型&#xff08;工作方式&#xff09;计时器BMA&#xff08;以太网&#xff09;broadcast &#xff0c;需要DR/BDR的选举hello&#xff1a;10s…