React-redux 实战案例,自定义useSelector

创建一个新的 React 工程,并配置 Redux 和 Ant Design,你可以按以下步骤操作。我将使用 create-react-app 脚手架工具来快速创建一个基于 TypeScript 的 React 项目

1. 创建新项目

使用 create-react-app 创建一个新的 React 项目,带 TypeScript 支持:

npx create-react-app my-project --template typescript

进入到项目目录:

cd my-project

2. 安装依赖

在项目目录下,安装必要的依赖项:

npm install redux react-redux immer antd
npm install @types/react @types/react-dom @types/react-redux --save-dev

3. 项目结构

my-project/
├── src/
│   ├── components/
│   │   └── GiftPanel/
│   │       └── index.tsx
│   ├── redux/
│   │   ├── index.tsx
│   │   ├── init-store.ts
│   ├── constants.ts
│   ├── utils/logger.ts
│   ├── App.tsx
│   └── index.tsx
├── package.json
└── tsconfig.json

如果报错:

情况1:Cannot find module 'ajv/dist/compile/codegen'

删除 node_modules
重新 npm install

安装特定依赖的版本

如果使用特定的库如 react-scripts,它可能需要特定版本的 ajv。尝试以下命令: 

npm install ajv@^8.0.0

检查 package.json

确保 package.json 中的依赖项没有版本冲突:

{"name": "my-project","version": "0.1.0","private": true,"dependencies": {"@testing-library/jest-dom": "^5.17.0","@testing-library/react": "^13.4.0","@testing-library/user-event": "^13.5.0","@types/jest": "^27.5.2","@types/node": "^16.18.19","ajv": "^8.17.1","antd": "^5.2.2","immer": "^10.1.1","react": "^18.2.0","react-dom": "^18.2.0","react-redux": "^8.0.5","react-scripts": "5.0.1","redux": "^4.2.1","typescript": "^4.9.5","web-vitals": "^2.1.4"},"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject"},"eslintConfig": {"extends": ["react-app","react-app/jest"]},"browserslist": {"production": [">0.2%","not dead","not op_mini all"],"development": ["last 1 chrome version","last 1 firefox version","last 1 safari version"]},"devDependencies": {"@types/react": "^18.0.26","@types/react-dom": "^18.0.10","@types/react-redux": "^7.1.23"}
}

情况2:

可能问题

  1. produce 函数的参数类型问题。
  2. TypeScript 类型声明问题。

错误修复

可以尝试以下步骤来修复你的代码: 

  1. 检查 immer 的版本:确保 immer 库已正确安装,并且版本与项目需求兼容。
检查 TypeScript 版本
确保项目使用的 TypeScript 版本兼容项目中的依赖:
npm install typescript@latest --save-dev
import { createStore, combineReducers, AnyAction } from 'redux';
import produce, { enableAllPlugins, Draft } from 'immer';
import { useSelector as useReactReduxSelector } from 'react-redux';
import { initStore, StoreState } from './init-store';
import logger from '../utils/logger';enableAllPlugins();function reducer(state: StoreState = initStore, action: AnyAction): StoreState {const { type, payload } = action;switch (type) {case 'PRODUCE':const { cb } = payload;return produce(state, (draft: Draft<StoreState>) => cb(draft));default:return state;}
}const reducers = {updateReducer: reducer,
};const baseAction = (cb: (draftState: Draft<StoreState>) => void) => ({type: 'PRODUCE',payload: { cb }
});export const actions = { baseAction };export function useSelector<U>(selector: (state: StoreState) => U,equalityFn?: ((left: any, right: any) => boolean) | undefined
) {return useReactReduxSelector<{ updateReducer: StoreState }, U>((state) => selector(state.updateReducer),equalityFn);
}const store = createStore(combineReducers(reducers));store.subscribe(() => {logger.log('store update========', JSON.stringify(store.getState()));
});export default store;

4. 创建和调整文件

  • src/redux/init-store.ts
import { AppState } from '../constants';// 定义 Order 和 User 接口
interface Order {orderId: number;userName: string;productName: string;price: number;status: '已支付' | '未支付';
}interface User {userId: number;userName: string;
}// 定义 StoreState 接口
export interface StoreState {testState: number;appState: AppState;hasGetGiftList: boolean;users: User[];orders: Order[];
}export const initStore: StoreState = {testState: 0,appState: AppState.Init,hasGetGiftList: false,users: [],orders: [{ orderId: 1, userName: 'Alice', productName: 'Product A', price: 100, status: '已支付' },{ orderId: 2, userName: 'Bob', productName: 'Product B', price: 150, status: '未支付' },{ orderId: 3, userName: 'Carol', productName: 'Product C', price: 200, status: '已支付' },{ orderId: 4, userName: 'David', productName: 'Product D', price: 250, status: '未支付' },{ orderId: 5, userName: 'Eve', productName: 'Product E', price: 300, status: '已支付' },{ orderId: 6, userName: 'Frank', productName: 'Product F', price: 350, status: '未支付' },{ orderId: 7, userName: 'Grace', productName: 'Product G', price: 400, status: '已支付' },{ orderId: 8, userName: 'Heidi', productName: 'Product H', price: 450, status: '未支付' },{ orderId: 9, userName: 'Ivan', productName: 'Product I', price: 500, status: '已支付' },{ orderId: 10, userName: 'Judy', productName: 'Product J', price: 550, status: '未支付' },{ orderId: 11, userName: 'Mallory', productName: 'Product K', price: 600, status: '未支付' },{ orderId: 12, userName: 'Oscar', productName: 'Product L', price: 650, status: '已支付' },{ orderId: 13, userName: 'Peggy', productName: 'Product M', price: 700, status: '已支付' },] // 初始化的订单数据
};
  • src/redux/index.tsx
import { createStore, combineReducers, AnyAction } from 'redux';
import { produce } from 'immer';
import { useSelector as useReactReduxSelector } from 'react-redux';
import { initStore, StoreState } from './init-store';
import logger from '../utils/logger';function reducer(state: StoreState = initStore, action: AnyAction): StoreState {const { type, payload } = action;switch (type) {case 'PRODUCE':const { cb } = payload;return produce(state, cb);default:return state;}
}const reducers = {updateReducer: reducer,
};const baseAction = (cb: (draftState: StoreState) => void) => ({type: 'PRODUCE',payload: { cb }
});export const actions = { baseAction };export function useSelector<U>(selector: (state: StoreState) => U,equalityFn?: ((left: any, right: any) => boolean) | undefined
) {return useReactReduxSelector<{ updateReducer: StoreState }, U>((state) => selector(state.updateReducer),equalityFn);
}const store = createStore(combineReducers(reducers));store.subscribe(() => {logger.log('store update========', JSON.stringify(store.getState()));
});export default store;
  • src/constants.ts
export enum AppState {Init,View,Setting
}
  • src/utils/logger.ts
const logger = {log: console.log
};export default logger;
  • src/components/GiftPanel/index.tsx
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { Table, Button, Modal, Form, Input, InputNumber, Select, Popconfirm, message, Pagination, ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN'; // 引入中文语言包
import { actions, useSelector } from '../../redux';interface Order {orderId: number;userName: string;productName: string;price: number;status: '已支付' | '未支付';
}const { Option } = Select;const GiftPanel = () => {const dispatch = useDispatch();const orders = useSelector((state) => state.orders);const [isAddModalVisible, setIsAddModalVisible] = useState(false);const [isViewModalVisible, setIsViewModalVisible] = useState(false);const [form] = Form.useForm();const [currentOrder, setCurrentOrder] = useState<Order | null>(null);const [currentPage, setCurrentPage] = useState(1);const [pageSize, setPageSize] = useState(10);const showAddModal = () => {setIsAddModalVisible(true);};const handleAddOk = () => {form.validateFields().then(values => {dispatch(actions.baseAction(draftState => {draftState.orders.push({ ...values, orderId: Date.now() });}));setIsAddModalVisible(false);form.resetFields();}).catch(info => {console.log('Validate Failed:', info);});};const handleAddCancel = () => {setIsAddModalVisible(false);form.resetFields();};const showViewModal = (order: Order) => {setCurrentOrder(order);setIsViewModalVisible(true);};const handleViewCancel = () => {setIsViewModalVisible(false);setCurrentOrder(null);};const handleDelete = (orderId: number) => {dispatch(actions.baseAction(draftState => {draftState.orders = draftState.orders.filter(order => order.orderId !== orderId);}));message.success('删除成功');};const handlePageChange = (page: number, pageSize?: number) => {setCurrentPage(page);if (pageSize) {setPageSize(pageSize);}};const columns = [{title: '订单ID',dataIndex: 'orderId',key: 'orderId',},{title: '用户名',dataIndex: 'userName',key: 'userName',},{title: '商品名称',dataIndex: 'productName',key: 'productName',},{title: '价格',dataIndex: 'price',key: 'price',},{title: '支付状态',dataIndex: 'status',key: 'status',},{title: '操作',key: 'action',render: (text: any, record: Order) => (<div><Button onClick={() => showViewModal(record)} style={{ marginRight: 8 }}>查看</Button><Popconfirmtitle="确认删除吗?"onConfirm={() => handleDelete(record.orderId)}okText="确认"cancelText="取消"><Button danger>删除</Button></Popconfirm></div>),},];// 计算当前页的数据const startIndex = (currentPage - 1) * pageSize;const endIndex = startIndex + pageSize;const currentData = orders.slice(startIndex, endIndex);return (<ConfigProvider locale={zhCN}><div><Button type="primary" onClick={showAddModal} style={{ marginBottom: '10px' }}>增加订单</Button><Tablecolumns={columns}dataSource={currentData}rowKey="orderId"pagination={false}/><div style={{ textAlign: 'right', marginTop: 20,display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}><Paginationtotal={orders.length}current={currentPage}pageSize={pageSize}onChange={handlePageChange}showTotal={total => `共 ${total} 条`}showSizeChangershowQuickJumper/></div><Modaltitle="添加订单"visible={isAddModalVisible}onOk={handleAddOk}onCancel={handleAddCancel}okText="添加"cancelText="取消"><Form form={form} layout="vertical" name="form_in_modal"><Form.Itemname="userName"label="用户名"rules={[{ required: true, message: '请输入用户名!' }]}><Input /></Form.Item><Form.Itemname="productName"label="商品名称"rules={[{ required: true, message: '请输入商品名称!' }]}><Input /></Form.Item><Form.Itemname="price"label="价格"rules={[{ required: true, message: '请输入价格!' }]}><InputNumber style={{ width: '100%' }} /></Form.Item><Form.Itemname="status"label="支付状态"rules={[{ required: true, message: '请选择支付状态!' }]}><Select><Option value="已支付">已支付</Option><Option value="未支付">未支付</Option></Select></Form.Item></Form></Modal><Modaltitle="查看订单"visible={isViewModalVisible}onCancel={handleViewCancel}footer={null}>{currentOrder && (<div><p>订单ID: {currentOrder.orderId}</p><p>用户名: {currentOrder.userName}</p><p>商品名称: {currentOrder.productName}</p><p>价格: {currentOrder.price}</p><p>支付状态: {currentOrder.status}</p></div>)}</Modal></div></ConfigProvider>);
};export default GiftPanel;
  • src/App.tsx
import React from 'react';
import GiftPanel from './components/GiftPanel';const App = () => (<div><GiftPanel /></div>
);export default App;
  • src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux';
import App from './App';ReactDOM.render(<Provider store={store}><App /></Provider>,document.getElementById('root')
);

5. 运行项目

在项目目录下运行:

npm start

这将启动开发服务器,打开浏览器访问 http://localhost:3000 以查看你的应用。

通过这个过程,你创建了一个带有 Redux 状态管理和 Ant Design 组件的 React 项目。你可以根据这个基础继续扩展和个性化你的应用

详细解释这个项目的工作原理。这个项目主要是一个使用 React 和 Redux 进行状态管理的订单管理页面,并且使用了 Ant Design 组件库来构建用户界面。以下是详细的解释:

1. 文件结构

项目的主要文件包括:

  • init-store.ts: 这个文件初始化了 Redux 的 store 以及一些假数据。
  • GiftPanel/index.tsx: 这个文件包含了主组件,用于展示订单列表,并包含分页、添加订单和删除订单的功能。

2. init-store.ts

这个文件定义了应用的初始状态并提供了一些假数据。

主要内容:
  • 接口定义:定义了 Order 和 StoreState 接口。Order 接口描述了订单对象的结构,而 StoreState 是应用状态的结构。
  • 初始状态:定义了包含初始化订单数组的 initStore,这些数据在应用加载时展示在表格中
import { AppState } from '../constants';interface Order {orderId: number;userName: string;productName: string;price: number;status: '已支付' | '未支付';
}export interface StoreState {testState: number;appState: AppState;hasGetGiftList: boolean;users: User[];orders: Order[];
}export const initStore: StoreState = {testState: 0,appState: AppState.Init,hasGetGiftList: false,users: [],orders: [/* 初始订单数据 */]
};

3. GiftPanel/index.tsx

这个文件是应用的主要组件,包含表格展示和操作的逻辑。

状态管理:
  • 使用 useState 来管理本地状态,如模态框的可见性、当前页码和页大小。
  • 使用 useSelector 从 Redux store 中获取订单数据。
  • 使用 useDispatch 进行状态更新。
主要功能:
  1. 显示订单表格

    • 使用 Ant Design 的 Table 组件展示订单数据。表格数据来源于 Redux store。
    • 表头(columns)定义了订单的字段和操作按钮。
  2. 分页

    • 使用 Ant Design 的 Pagination 组件来实现分页功能。
    • 通过计算 currentPage 和 pageSize,确定当前页面显示的数据片段。
  3. 添加订单

    • 使用 Ant Design 的 Modal 和 Form 组件,通过表单输入新订单数据。
    • 表单验证通过后,触发 handleAddOk 更新 Redux store。
  4. 删除订单

    • 使用 Popconfirm 组件确认删除操作。
    • 确认删除后,通过 handleDelete 更新 Redux store。
  5. 查看订单

    • 点击查看按钮,展示订单详细信息。
// 初始化状态
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
const [isViewModalVisible, setIsViewModalVisible] = useState(false);
const [form] = Form.useForm();
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);// 页码变化处理
const handlePageChange = (page: number, pageSize?: number) => {setCurrentPage(page);if (pageSize) {setPageSize(pageSize);}
};// 计算当前页的数据
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const currentData = orders.slice(startIndex, endIndex);return (<ConfigProvider locale={zhCN}><div><Button type="primary" onClick={() => setIsAddModalVisible(true)} style={{ marginBottom: '10px' }}>增加订单</Button><Table columns={columns} dataSource={currentData} rowKey="orderId"pagination={false}/><div style={{ textAlign: 'right', marginTop: 20 }}><Paginationtotal={orders.length}current={currentPage}pageSize={pageSize}onChange={handlePageChange}showTotal={total => `共 ${total} 条`}showSizeChangershowQuickJumper/></div></div></ConfigProvider>
);

解释:

  1. 语言设置:使用 ConfigProvider 包裹组件并设置语言为中文。
  2. 状态管理:使用 useState 管理组件本地状态,如模态框的可见性和分页信息,使用 useSelector 从 Redux store 获取订单数据,使用 useDispatch 更新 Redux store。
  3. 订单表格展示:定义表格列结构,使用 Ant Design 的 Table 组件展示订单数据。
  4. 分页:使用 Pagination 组件实现分页,通过 handlePageChange 处理分页变化。
  5. 订单操作:实现添加、查看和删除订单的操作。

这个项目的核心在于利用 React 和 Redux 进行状态管理,并结合 Ant Design 组件库构建用户界面。

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

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

相关文章

【C++】list 类深度解析:探索双向链表的奇妙世界

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 如果你对string&#xff0c;vector还存在疑惑&#xff0c;欢迎阅读我之前的作品 &#xff1a; 之前文章&#x1f525;&#x1…

uniapp如何i18n国际化

1、正常情况下项目在代码生成的时候就已经有i18n的相关依赖&#xff0c;如果没有可以自行使用如下命令下载&#xff1a; npm install vue-i18n --save 2、创建相关文件 en文件下&#xff1a; zh文件下&#xff1a; index文件下&#xff1a; 3、在main.js中注册&#xff1a…

VScode-Java开发常用插件

中文——界面易读 字体主题——代码可观 头注释——项目信息明了 java开发包——java必备 git协作开发——版本控制

前端(3)——快速入门JaveScript

参考&#xff1a; 罗大富 JavaScript 教程 | 菜鸟教程 JavaScript 教程 1. JaveScript JavaScript 简称 JS JavaScript 是一种轻量级、解释型、面向对象的脚本语言。它主要被设计用于在网页上实现动态效果&#xff0c;增加用户与网页的交互性。作为一种客户端脚本语言&#…

FRP 实现内网穿透

如何通过 FRP 实现内网穿透&#xff1a;群晖 NAS 的 Gitea 和 GitLab 访问配置指南 在自建服务的过程中&#xff0c;经常会遇到内网访问受限的问题。本文将介绍如何利用 FRP&#xff08;Fast Reverse Proxy&#xff09;来实现内网穿透&#xff0c;以便在外网访问群晖 NAS 上的…

我们来学mysql -- EXPLAIN之select_type(原理篇)

EXPLAIN之select_type 题记select_type 题记 书接上文《 EXPLAIN之ID》2024美国大选已定&#xff0c;川普剑登上铁王座&#xff0c;在此过程中出谋划策的幕僚很重要&#xff0c;是他们决定了最终的执行计划在《查询成本之索引选择》中提到&#xff0c;explain的输出&#xff0…

uni-app快速入门(五)--判断运行环境及针对不同平台的条件编译

一、判断运行环境 在实际项目开发中&#xff0c;经常需要进行开发环境和生产环境的切换&#xff0c;uni-app可根据process.env.NODE_ENV判断当前运行环境是开发环境和生产环境&#xff0c;根据不同的环境调用不同的后台接口&#xff0c;具体实现方式: 在项目的static目录下建…

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…

构建客服知识库:企业效率提升的关键步骤

客服知识库是企业提升客户服务效率和质量的重要工具。它不仅帮助客服团队快速准确地回答客户问题&#xff0c;还能通过数据分析来优化服务流程和提升客户满意度。 1. 明确知识库的目标和范围 构建客服知识库的第一步是明确其目标和范围。这包括确定知识库的主要用户群体、需要…

Linux运维工程师推荐学习的开发语言

前言&#xff1a;会开发的运维和不会开发的运维可以说是两个世界的运维。 个人推荐python和go&#xff0c;前者可以做自动化运维&#xff0c;后者可以深挖k8s&#xff1b;最近就不先演示运维服务技术的部署和架构搭建了&#xff0c;在深挖自动化运维&#xff0c;为了让现在的工…

整合seata遇到的问题

自己遇到的问题&#xff0c;记录一下。 1、版本问题 我seata用的是1.7&#xff0c; 数据库驱动是 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.31</version><scope>…

从零到一:利用 AI 开发 iOS App 《震感》的编程之旅

在网上看到一篇关于使用AI开发的编程经历&#xff0c;分享给大家 作者是如何在没有 iOS 开发经验的情况下&#xff0c;借助 AI&#xff08;如 Claude 3 模型&#xff09;成功开发并发布《震感》iOS 应用。 正文开始 2022 年 11 月&#xff0c;ChatGPT 诞生并迅速引发全球关注。…

.netcore + postgis 保存地图围栏数据

一、数据库字段 字段类型选择(Type) 设置对象类型为&#xff1a;geometry 二、前端传递的Json格式转换 前端传递围栏的各个坐标点数据如下&#xff1a; {"AreaRange": [{"lat": 30.123456,"lng": 120.123456},{"lat": 30.123456…

系统掌握大语言模型提示词 - 从理论到实践

以下是我目前的一些主要个人标签&#xff1a; 6 年多头部大厂软件开发经验&#xff1b;1 年多 AI 业务应用经验&#xff0c;拥有丰富的业务提示词调优经验和模型微调经验。信仰 AGI&#xff0c;已经将 AI 通过自定义 Chatbot /搭建 Agent 融合到我的工作流中。头部大厂技术大学…

k8clone二进制工具迁移k8s中的无状态应用

1 概述 k8clone是一个简便的Kubernetes元数据克隆工具&#xff0c;它可以将Kubernetes元数据&#xff08;对象&#xff09;保存为本地压缩包&#xff0c;在恢复时可将这些元数据恢复到目标集群中&#xff08;已存在的资源不会被覆盖&#xff09;。它不依赖远程存储&#xff0c…

IDC 报告:百度智能云 VectorDB 优势数量 TOP 1

近日&#xff0c;IDC 发布了《RAG 与向量数据库市场前景预测》报告&#xff0c;深入剖析了检索增强生成&#xff08;RAG&#xff09;技术和向量数据库市场的发展趋势。报告不仅绘制了 RAG 技术的发展蓝图&#xff0c;还评估了市场上的主要厂商。在这一评估中&#xff0c;百度智…

计算机毕业设计Python+CNN卷积神经网络股票预测系统 股票推荐系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Nginx SSL+tomcat,使用request.getScheme() 取到https协议

架构上使用了 Nginx tomcat 集群, 且nginx下配置了SSL,tomcat no SSL,项目使用https和http协议。 发现 request.getScheme() //总是 http&#xff0c;而不是实际的http或https request.isSecure() //总是false&#xff08;因为总是http&#xff09; request.getRemoteAddr(…

机器学习 ---线性回归

目录 摘要&#xff1a; 一、简单线性回归与多元线性回归 1、简单线性回归 2、多元线性回归 3、残差 二、线性回归的正规方程解 1、线性回归训练流程 2、线性回归的正规方程解 &#xff08;1&#xff09;适用场景 &#xff08;2&#xff09;正规方程解的公式 三、衡量…

蓝桥杯c++算法学习【3】之思维与贪心(重复字符串、翻硬币、乘积最大、皮亚诺曲线距离【难】:::非常典型的必刷例题!!!)

别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 关注博主&#xff0c;更多蓝桥杯nice题目静待更新:) 思维与贪心 一、重复字符串 【问题描述】 如果一个字符串S恰好可以由某个字符串重复K次得到&#xff0c;我们就称S是K次重复字 符串…