【更新中】【React】基础版React + Redux实现教程(Vite + React + Redux + TypeScript)

本项目是一个在react中,使用 redux 管理状态的基础版实现教程,用简单的案例练习redux的使用,旨在帮助学习 redux 的状态管理机制,包括 storeactionreducerdispatch 等核心概念。

项目地址:https://github.com/YvetW/redux-mini-demo

注:项目中包含多个版本的代码,有助于理解redux,运行前请先选择需要的版本,修改目录名为src。

在这里插入图片描述

版本:

  • React v19
  • Redux v5
  • TypeScript
  • Vite

1. 初始化项目

使用 Vite 创建 React+TypeScript 项目:

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install

主要目录结构

my-app/
├── src/
│   ├── redux/
│   │   ├── action-types.ts  # 定义 Action Types
│   │   ├── actions.ts       # 定义 Actions
│   │   ├── reducers.ts      # 定义 Reducers
│   │   ├── store.ts         # 创建 Store
│   ├── App.tsx              # 组件主入口
│   ├── main.tsx             # 入口文件,渲染 App
│   ├── index.css            # 样式文件
├── package.json             # 依赖管理
├── tsconfig.json            # TypeScript 配置
└── vite.config.ts           # Vite 配置

2. 在 React 组件中使用 Redux

本版本不使用 react-redux@reduxjs/toolkit,而是直接使用 Redux 的原生 createStore API(已过时,但有助于理解 Redux 的核心概念)。

2.1 创建 Action Types (action-types.ts)

  • 在 Redux 中,所有的 action 都应有一个唯一的 type,定义 type 常量。
  • 例如:INCREMENTDECREMENTADD_MESSAGE
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_MESSAGE = 'add_message'

2.2 创建 Reducers (reducers.ts)

  • Redux 需要使用 reducers 函数处理 state 变化。
  • 创建两个 reducercountmessages
  • 根据不同 action 类型更新 state,返回新的 state
import {combineReducers} from 'redux';
import {ADD_MESSAGE, DECREMENT, INCREMENT} from './action-types.ts';
import {Action} from './actions.ts';// 管理count
const initCount: number = 0;function count(state: number = initCount, action: Action): number {console.log('count', state, action);switch (action.type) {case INCREMENT:return state + action.data;case DECREMENT:return state - action.data;default:return state;}
}// 管理messages
const initMessages: string[] = [];function messages(state: string[] = initMessages, action: Action): string[] {console.log('messages', state, action);switch (action.type) {case ADD_MESSAGE:return [action.data, ...state]default:return state;}
}export default combineReducers({count, messages});// 整体state状态结构: {count: 2, messages: ['xx', 'xxx']}

2.3 创建 Actions (actions.ts)

  • Actions 是 Redux store 唯一的数据来源。
  • 创建 incrementdecrementaddMessage action。
import {INCREMENT, DECREMENT, ADD_MESSAGE} from './action-types.ts';// 定义 CountAction 的类型
interface CountAction {type: typeof INCREMENT | typeof DECREMENT;data: number;
}// 定义 MessageAction 的类型
interface MessageAction {type: typeof ADD_MESSAGE;  // 只有 ADD_MESSAGE 类型data: string;  // data 是一个字符串
}// 联合类型
export type Action = CountAction | MessageAction;export const increment = (number: number): CountAction => ({type: INCREMENT, data: number});export const decrement = (number: number): CountAction => ({type: DECREMENT, data: number});export const add_message = (message: string): MessageAction => ({type: ADD_MESSAGE, data: message});

2.4 创建 Store (store.ts)

  • 通过 createStore(rootReducer) 创建 Redux store,集中管理应用状态。
import { createStore } from 'redux';
import reducers from './reducers'; // 包含多个reducer的reducerexport default createStore(reducers)

2.5 App.tsx 中使用 store

  • 通过 props.store.getState() 获取 state,控制组件渲染。
  • 通过 props.store.dispatch(action) 触发状态更新。
import {useState} from 'react';
import {Store} from 'redux';
import {Action, increment, decrement, add_message} from './redux/actions.ts';// 为 App 组件定义一个 Props 类型,这个类型包含 store
interface AppProps {store: Store<{count: number;messages: string[];}, Action>;
}function App({store}: AppProps) {const [selectedValue, setSelectedValue] = useState<number>(1);const [message, setMessage] = useState<string>('');const count = store.getState().count;const messages = store.getState().messages;function handleIncrement() {store.dispatch(increment(selectedValue));}function handleDecrement() {store.dispatch(decrement(selectedValue));}// 奇数增加function handleIncrementIfOdd() {// setCount 是异步的,如果 Redux 状态更新了,count 可能不会立即反映出来,直接从 store.getState() 读取最新的 countif (store.getState().count % 2 === 1) {store.dispatch(increment(selectedValue));}}// 异步增加function handleIncrementIfAsync() {setTimeout(() => {store.dispatch(increment(selectedValue));}, 1000);}function handleAdd() {if (message.trim()) {store.dispatch(add_message(message));setMessage('');}}return (<div className="App"><div className="count">click <span>{count}</span> times</div><selectid="number" value={selectedValue}// <option> 的 value 默认是字符串类型(即使它是一个数字),这里强制转换为数字onChange={event => setSelectedValue(Number(event.target.value))}>{[1, 2, 3, 4, 5].map((item) => (<option value={item} key={item}>{item}</option>))}</select><button onClick={handleIncrement}>+</button><button onClick={handleDecrement}>-</button><button onClick={handleIncrementIfOdd}>increment if odd</button><button onClick={handleIncrementIfAsync}>increment async</button><hr /><inputtype="text"value={message}onChange={event => setMessage(event.target.value)}/><button onClick={handleAdd}>add text</button><ul>{messages.map((msg, index) => (<li key={index}>{msg}</li>))}</ul></div>);
}export default App;

2.6 main.tsx 中订阅 Store 并触发渲染

  • createRoot() 挂载 App 组件。
  • 通过 store.subscribe() 监听 state 变化,每次 dispatch 触发 state 变更时重新渲染 App
  • React 18 及以上使用 createRoot() 进行渲染,注意避免重复 createRoot() 调用。
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import './index.scss';
import App from './App';
import store from './redux/store.ts';const root = createRoot(document.getElementById('root')!); // 只调用一次 createRoot()// root.render() 负责后续更新
function render() {root.render(<StrictMode><App store={store} /></StrictMode>,);
}// 初次渲染
render();// 订阅 store,state 变化时触发 render
store.subscribe(render);

3. 自定义 redux 库

自己定义一个简易的 redux 库,主要针对 createStore 和 combineReducers 的核心概念。

简单来说,createStore 用来创建存储和管理应用状态的对象,而 combineReducers 用来将多个子 reducer 合并成一个单一的 reducer。

注:完整版请看项目代码。

3.1 createStore

  • 作用:用于创建一个 Redux Store,负责管理应用的状态。
  • 接收的参数:
    • reducer:根 reducer,决定如何根据 action 更新 state
    • 可选的 preloadedState:初始化时的状态。
  • 返回的store对象包含:
    • dispatch:分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listener
    • getState:得到内部管理的state对象
    • subscribe:监听 state 的变化,并在状态变化时触发回调。
export function createStore<S, A extends Action>(rootReducer: Reducer<S, A>): Store<S> {// 内部state,第一次调用reducer得到初始状态并保存let state: S;// 内部保存n个listener的数组const listeners: (() => void)[] = [];// 初始调用reducer得到初始state值state = rootReducer(undefined, {type: '@mini-redux'} as A);// 得到内部管理的state对象function getState() {return state;}// 分发action,会触发reducer调用,返回一个新的state,调用所有绑定的listenerfunction dispatch(action: Action) {// 调用reducer,得到一个新的state,保存state = rootReducer(state, action as A);// 调用listeners中所有的监视回调函数listeners.forEach(listener => listener());}// 订阅state变化,产生新的状态,也就是dispatch时才执行function subscribe(listener: () => void) {listeners.push(listener); // 将listener加入到订阅列表// 返回一个取消订阅的函数(main.ts 通常不需要手动取消,只执行一次,store.subscribe(render) 只执行一次,不会不断创建新的 listener,所以 不会造成内存泄漏。)return () => {const index = listeners.indexOf(listener);if (index >= 0) {listeners.splice(index, 1);}};}// 返回一个store对象return {getState, dispatch, subscribe};
}

3.2 combineReducers

  • 作用:将多个子 reducer 组合成一个根 reducer。
  • 接收的参数:
    • reducers:一个对象,其中包含多个子 reducer,每个子 reducer 负责管理 state 的一部分。
  • 这个函数返回一个新的 reducer,能够根据 action 调用每个子 reducer,更新对应的 state 部分。
// 接收一个包含多个reducer函数的对象,返回一个新的reducer函数
export function combineReducers<S, A extends Action>(reducers: { [K in keyof S]: Reducer<S[K], A> }
) {return function (state: Partial<S> = {} as S, action: A): S { // 这个 rootReducer 函数会传给 createStore()return Object.keys(reducers).reduce((newState, key) => {// 确保类型安全:key 是 S 类型的有效键newState[key as keyof S] = reducers[key as keyof S](state[key as keyof S], action)return newState}, {} as S)};
}

结论

本项目通过最基础的 Redux 实现,帮助理解 Redux 的核心概念,包括 storeactionreducerdispatch,同时避免 react-redux 的复杂性,适合初学者学习 Redux 的工作原理。

如果要在实际项目中使用 Redux,建议使用 @reduxjs/toolkitreact-redux,可以大幅减少 Redux 代码量,提高开发效率。

后续会继续编写使用 @reduxjs/toolkitreact-redux 的教程,欢迎点赞收藏关注!感恩比心~

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

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

相关文章

【MySQL】从零开始:掌握MySQL数据库的核心概念(四)

人们之所以不愿改变&#xff0c;是因为害怕未知。但历史唯一不变的事实&#xff0c;就是一切都会改变。 前言 这是我自己学习mysql数据库的第四篇博客总结。后期我会继续把mysql数据库学习笔记开源至博客上。 上一期笔记是关于mysql数据库的表格约束&#xff0c;没看的同学可以…

AP 场景架构设计(一) :OceanBase 读写分离策略解析

说明&#xff1a;本文内容对应的是 OceanBase 社区版&#xff0c;架构部分不涉及企业版的仲裁副本功能。OceanBase社区版和企业版的能力区别详见&#xff1a; 官网链接。 概述​ 当两种类型的业务共同运行在同一个数据库集群上时&#xff0c;这对数据库的配置等条件提出了较高…

CPU架构和微架构

CPU架构&#xff08;CPU Architecture&#xff09; CPU架构是指处理器的整体设计框架&#xff0c;定义了处理器的指令集、寄存器、内存管理方式等。它是处理器设计的顶层规范&#xff0c;决定了软件如何与硬件交互。 主要特点&#xff1a; 指令集架构&#xff08;ISA, Instr…

6.4 模拟专题:LeetCode1419.数青蛙

1.题目链接&#xff1a;数青蛙 - LeetCode 2.题目描述 给定一个字符串 croakOfFrogs&#xff0c;表示青蛙的鸣叫声序列。每个青蛙必须按顺序发出完整的 “croak” 字符&#xff0c;且多只青蛙可以同时鸣叫。要求计算最少需要多少只青蛙才能完成该字符串&#xff0c;若无法完成…

Linux 搭建dns主域解析,和反向解析

#!/bin/bash # DNS主域名服务 # user li 20250325# 检查当前用户是否为root用户 # 因为配置DNS服务通常需要较高的权限&#xff0c;只有root用户才能进行一些关键操作 if [ "$USER" ! "root" ]; then# 如果不是root用户&#xff0c;输出错误信息echo "…

Leetcode 二进制求和

java solution class Solution {public String addBinary(String a, String b) {StringBuilder result new StringBuilder();//首先设置2个指针, 从右往左处理int i a.length() - 1;int j b.length() - 1;int carry 0; //设置进位标志位//从2个字符串的末尾向前遍历while(…

【NLP 49、提示工程 prompt engineering】

目录 一、基本介绍 语言模型生成文本的基本特点 提示工程 prompt engineering 提示工程的优势 使用注意事项 ① 安全问题 ② 可信度问题 ③ 时效性与专业性 二、应用场景 能 ≠ 适合 应用场景 —— 百科知识 应用场景 —— 写文案 应用场景 —— 解释 / 编写…

【NLP 43、文本生成任务】

目录 一、生成式任务 二、seq2seq任务 1.模型结构 2.工作原理 3.局限性 三、自回归语言模型训练 Decoder only 四、自回归模型结构&#xff1a;文本生成任务 —— Embedding LSTM 代码示例 &#x1f680; 数据文件 代码流程 Ⅰ、模型初始化 Ⅱ、前向计算 代码运行流程 Ⅲ、加载…

vscode 通过Remote-ssh远程连接服务器报错 could not establish connection to ubuntu

vscode 通过Remote-ssh插件远程连接服务器报错 could not establish connection to ubuntu&#xff0c;并且出现下面的错误打印&#xff1a; [21:00:57.307] Log Level: 2 [21:00:57.350] SSH Resolver called for "ssh-remoteubuntu", attempt 1 [21:00:57.359] r…

Linux之编辑器vim命令

vi/vim命令&#xff1a; 终端下编辑文件的首选工具&#xff0c;号称编辑器之神 基本上分为三种模式&#xff0c;分别是 命令模式&#xff08;command mode&#xff09;>输入vi的命令和快捷键&#xff0c;默认打开文件的时候的模式插入模式&#xff08;insert mode&#x…

第一天学爬虫

阅读提示&#xff1a;我今天才开始尝试爬虫&#xff0c;写的不好请见谅。 一、准备工具 requests库&#xff1a;发送HTTP请求并获取网页内容。BeautifulSoup库&#xff1a;解析HTML页面并提取数据。pandas库&#xff1a;保存抓取到的数据到CSV文件中。 二、爬取步骤 发送请求…

MySQL实战(尚硅谷)

要求 代码 # 准备数据 CREATE DATABASE IF NOT EXISTS company;USE company;CREATE TABLE IF NOT EXISTS employees(employee_id INT PRIMARY KEY,first_name VARCHAR(50),last_name VARCHAR(50),department_id INT );DESC employees;CREATE TABLE IF NOT EXISTS departments…

windows下安装sublime

sublime4 alpha 4098 版本 下载 可以根据待破解的版本选择下载 https://www.sublimetext.com/dev crack alpha4098 的licence 在----- BEGIN LICENSE ----- TwitterInc 200 User License EA7E-890007 1D77F72E 390CDD93 4DCBA022 FAF60790 61AA12C0 A37081C5 D0316412 4584D…

激光线检测算法的FPGA实现

激光线检测算法的FPGA实现 1. 常见的激光线检测算法 激光线检测中常用的三种算法 MAX&#xff08;最大值法&#xff09;、THRESH&#xff08;阈值法&#xff09;、COG&#xff08;灰度重心法&#xff09; 分别具有以下特点和工作原理&#xff1a; 1.1 MAX&#xff08;最大值法…

小样本微调大模型

一、环境搭建 conda create -n dseek python=3.10 conda activate dseek pip install bitsandbytes Pip install numpy python -m pip install --upgrade pip setuptools wheel 安装cuda,torch,Unsloth, huggingface,wandb等,见前述章节; 微调服务器配置:单机笔记本显卡4…

深入理解指针(2)(C语言版)

文章目录 前言一、数组名的理解二、使用指针访问数组三、一维数组传参的本质四、冒泡排序五、二级指针六、指针数组七、指针数组模拟二维数组总结 前言 在上一篇文章中&#xff0c;我们初步了解了指针的基本概念和用法。今天&#xff0c;我们将继续深入探索指针在数组、函数传…

高效内存管理:x86-64架构中的分页机制

在 x86-64 架构的世界里&#xff0c;内存分页机制扮演着举足轻重的角色&#xff0c;它就像是一座桥梁&#xff0c;连接着虚拟地址与物理地址。简单来说&#xff0c;内存分页机制就是将线性地址&#xff08;也就是虚拟地址&#xff09;切分成一个个固定大小的页&#xff0c;并把…

统一开放世界与开放词汇检测:YOLO-UniOW无需增量学习的高效通用开放世界目标检测框架

目录 一、摘要 二、引言 三、相关工作 开放词汇对象检测 开放世界目标检测 参数高效学习 四、高效通用的开放世界目标检测 问题定义 高效的自适应决策学习 开放世界通配符学习 五、Coovally AI模型训练与应用平台 六、实验 数据集 评价指标 实施细节 定量结果 …

fileinclude

##解题思路 场景首页没有什么提示&#xff0c;只有个flag在flag.php中&#xff0c;而且需要更改language&#xff0c;还有个index.php的路径&#xff0c;先记住它 习惯性查看源代码&#xff0c;得到了题目真正的内容&#xff0c;关键在于lan变量读取我们传入的Cookie值中的lang…

链表-LeetCode

这里写目录标题 1 排序链表1.1 插入法 O&#xff08;n&#xff09;1.2 归并排序 1 排序链表 1.1 插入法 O&#xff08;n&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullpt…