「2024」React 状态管理入门

概念

简单来说,状态指的是某一时刻应用中的数据或界面的呈现。这些数据可能包括用户填写表单的信息、应用内的用户偏好设置、应用的页面/路由状态、或者任何其他可能改变UI的信息。

在这里插入图片描述

状态管理是前端开发中处理用户界面(UI)状态的过程,在复杂应用中尤其重要。随着应用规模的增长,管理不同组件和模块之间的状态变得越来越复杂。

在没有状态管理的情况下,应用组件通常需要进行大量的props传递(即将数据从一个组件传递到另一个组件),或者使用事件来通信,这在小型或简单的应用中是可行的。但在大型或复杂的项目中,这些方法难以维护和跟踪状态的变化,也会使得组件间耦合度增加,随之而来的问题包括难以追踪状态的变化源头和状态更新的影响。

为了解决这个问题,出现了各种状态管理库/模式,它们帮助开发者集中管理状态、提供更可预测的状态更新机制,并通过某种形式的全局状态存储,实现不同组件间的通信,而无需直接相互引用。比较知名的状态管理库包括 Redux、MobX、Zustand 等,各自有不同的实现原理和适用场景。例如,使用Redux的React应用会有一个单一的全局状态对象(store),所有的状态变化都通过一套严格的流程(actions -> reducers -> store)来管理,而React组件通过连接(connect)到这个全局状态来获取自己所需的状态部分,同时也可以触发状态的更新。这样,状态的变化就变得可追踪且可预测,而组件之间的关系也变得更为清晰。

状态管理的核心概念就是提高状态的可管理性,降低组件间的耦合度,并增强大型应用的可维护性。

状态管理工具介绍

目前实现状态管理的方式大概有下面几种:

  • Context API
  • Redux
  • Zustand
  • Mobx
  • Recoil
  • Jotai

Context API

Context 是 React 内置的状态管理工具,使用 Context 提供的 useContext + useReducer 可以实现一个基本的 Redux 功能。
示例:

  1. 首先创建一个 Context

import React, { createContext, useState, ReactNode } from 'react';// 定义 context 的类型
interface IContext {state: string;updateState: (newState: string) => void;
}// 创建一个 Context 对象, 初始值为 undefined
export const MyContext = createContext<IContext | undefined>(undefined);interface IProviderProps {children: ReactNode;
}// 创建 Provider 组件
export const MyProvider: React.FC<IProviderProps> = ({ children }) => {const [state, setState] = useState<string>('初始状态');const updateState = (newState: string) => {setState(newState);};return (<MyContext.Provider value={{ state, updateState }}>{children}</MyContext.Provider>);
};
  1. 接下来在组件树中使用 MyProvider 来包裹顶层组件
import React from 'react';
import { MyProvider } from './MyContext';
import ChildComponent from './ChildComponent';const App: React.FC = () => {return (<MyProvider><ChildComponent /></MyProvider>);
};export default App;
  1. 最后,在需要访问状态的子组件中,使用 useContext Hook:
import React, { useContext } from 'react';
import { MyContext } from './MyContext';const ChildComponent: React.FC = () => {const context = useContext(MyContext);if (!context) {throw new Error('useContext must be inside a Provider with a value');}const { state, updateState } = context;const handleChange = () => {updateState('更新后的状态');};return (<div><p>{state}</p><button onClick={handleChange}>更改状态</button></div>);
};export default ChildComponent;
优缺点

优点:

  • 作为 React 内置的 Hook,不需要引入第三方库,使用起来较为方便
    缺点:
  • Context 只能存储单一的值,当数据量大起来的时候,需要创建大量的 Context。
  • 使用 React Context 的一个已知的性能问题是,当一个 Context 的值发生变化时,所有消费该 Context 的组件都将重新渲染,不管它们是否真的依赖于这个变化的部分。在组件树较大且更新频繁的情况下,这可能会导致不必要的渲染,并对性能造成负担。

针对性能问题的优化策略:

  1. 拆分 Context:如果你的 Context 对象非常庞大,并且有不同的部分被不同的组件使用,那么拆分 Context 是一个很好的方式。通过将状态拆分为更小的、独立的 Contexts,组件可以订阅它们实际所需要的那一部分状态,从而减少不必要的渲染。
  2. 优化子组件:
  • 使用 React.memo:它是 React 提供的一个高阶组件,它会对组件的 props 进行浅比较,这样只有当组件的 props 发生变化时,组件才会重新渲染。
  1. 使用多个状态提供者:在大型应用中,可以在多个层级上设置提供者,而不是仅仅在应用的顶层。这样可以控制渲染发生的具体位置和范围。
  2. 状态选择:传递一个函数给 Context 消费者,而不是直接传递整个状态对象。这个函数可以从全局状态中选择组件特定需要的部分状态。这样不仅可以避免组件不必要的更新,同时还能使组件的意图更加明显。
  3. 避免在 Context Value 中传递一个新的对象或者函数:因为这会在每次 Provider 渲染时创建新的引用,导致所有消费者组件重新渲染。你可以通过使用 useCallback 来缓存函数,以及使用 useMemo 来缓存计算得出的值。
  4. 拆分状态和更新函数:有时你可能有一个大的状态对象,并且更新函数不经常变化。你可以创建两个 Contexts —— 一个只传递状态,另一个只传递更新函数 —— 这样当更新函数不变时,依赖状态的组件就不会在更新函数变化时重新渲染。
  5. 用状态管理库:如果你发现 Context 不适合你的应用需求或者你需要更细粒度的控制,可能需要使用如 Redux、MobX 或 Recoil 这样的状态管理库。

Redux

redux是GitHub star数和周下载量都最多的状态管理工具。

使用示例:

  1. 安装必要的包
npm install @reduxjs/toolkit react-redux

或者如果你在用 yarn:

yarn add @reduxjs/toolkit react-redux
  1. 创建 Redux 状态和动作

首先,定义应用的状态和动作。在 store.ts 文件中,使用 createSlice@reduxjs/toolkit 创建一个 slice,包含了状态的初识值、reducer 和自动生成的动作。

// store.ts
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';// 定义状态的类型
interface CounterState {value: number;
}// 初始状态
const initialState: CounterState = {value: 0,
};// 创建 slice
const counterSlice = createSlice({name: 'counter',initialState,reducers: {// 定义 reducer 和对应的动作incremented: state => {state.value += 1;},decremented: state => {state.value -= 1;},incrementedByAmount: (state, action: PayloadAction<number>) => {state.value += action.payload;},},
});export const { incremented, decremented, incrementedByAmount } = counterSlice.actions;// 配置 store
const store = configureStore({reducer: {counter: counterSlice.reducer,},
});export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;export default store;
  1. 设置 Provider

接下来,在应用的根组件中使用 Provider 包装应用,以便可以在组件树中的任意位置访问 Redux store。

// index.tsx 或 App.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';ReactDOM.render(<React.StrictMode><Provider store={store}><App /></Provider></React.StrictMode>,document.getElementById('root')
);
  1. 访问状态和调用动作

在组件中使用 useSelector 从 Redux store 选择状态,并使用 useDispatch 发送动作。

// Counter.tsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from './store';
import { incremented, decremented, incrementedByAmount } from './store';const Counter: React.FC = () => {const count = useSelector((state: RootState) => state.counter.value);const dispatch = useDispatch<AppDispatch>();return (<div><div>{count}</div><button onClick={() => dispatch(incremented())}>Increment</button><button onClick={() => dispatch(decremented())}>Decrement</button><button onClick={() => dispatch(incrementedByAmount(5))}>Increment by 5</button></div>);
};export default Counter;

在这个示例中,Counter 组件通过 useSelector 读取 Redux store 中的 counter 值,并且使用按钮来调用动作 incrementeddecrementedincrementedByAmount 来更新状态。

这只是使用 Redux 进行状态管理的一个简单示例,根据实际项目的复杂性,你可能需要更多的 reducers、middewares、selectors 或其他逻辑。

优缺点

优点:可扩展性高 & 繁荣的社区
缺点:大量的模版代码 & 状态量大起来后,有可能会出现性能问题
(要是都往redux里存,可想而知,每次action过来把所有reducer跑一遍。虽然 Redux后面开始支持拆分 store,异步加载 store,没到这个业务的场景的时候不加载这个业务的 store。但是如果业务耦合较为严重,那还是跑不掉)

Zustand

Zustand 是一个简单的、快速的状态管理解决方案,它不局限于 React 组件的层次结构,这就意味着你可以在任何地方访问状态,而无需使用 Provider 包装你的应用。

使用示例:

  1. 安装 Zustand

首先,你需要安装 Zustand。你可以通过 npm 或 yarn 来安装它:

npm install zustand

或者

yarn add zustand
  1. 创建一个 store

你可以创建一个新的文件,例如 useStore.ts,并在其中定义你的状态和行为:

// useStore.ts
import create from 'zustand'// 定义状态和动作的类型
interface CounterState {count: number;increment: () => void;decrement: () => void;reset: () => void;
}// 使用 create 创建一个 zustand store
const useStore = create<CounterState>(set => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),decrement: () => set(state => ({ count: state.count - 1 })),reset: () => set({ count: 0 }),
}));export default useStore;
  1. 在组件中使用 store

然后你可以在组件中使用这个状态,如下所示:

// Counter.tsx
import React from 'react';
import useStore from './useStore';const Counter: React.FC = () => {// 使用 store 中的状态和行为const { count, increment, decrement, reset } = useStore();return (<div><h2>Count: {count}</h2><button onClick={increment}>+</button><button onClick={decrement}>-</button><button onClick={reset}>Reset</button></div>);
};export default Counter;

在这个例子中,Counter 组件使用了从 useStore 挂钩返回的 count 状态和三个更新这个状态的方法:increment, decrement, 和 reset

使用 Zustand,你不需要担心传统 Redux 所有的模板代码或 Context API 的潜在性能问题。 Zustand 提供了一个更灵活和轻量级的状态管理方案,特别适合在简单或中等复杂度的 React 应用中使用。

其他工具 TODO

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

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

相关文章

MATLAB 构建协方差矩阵,解算特征值和特征向量(63)

MATLAB 局部点云构建协方差矩阵,解算特征值和特征向量(63) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 对于某片有待分析的点云,我们希望构建协方差矩阵,计算特征值和特征向量,这是很多算法必要的分析方法,这里提供完整的计算代码(验证正确) !!! 特别需要注意…

Linux上的可执行文件在Windows上是不能运行的

一、概要 1、可执行文件的格式 Linux上的可执行文件是elf格式的 Windows上的可执行文件是exe格式的 Linux上的可执行文件在Windows上是不能运行的 2、程序的普通构建与静态构建 普通构建&#xff1a; 一个.c文件&#xff0c;用gcc命令编译成可执行文件(程序)&#xff0c…

[lesson22]对象的销毁

对象的销毁 对象的销毁 生活中的对象都是被初始化后才上市的 生活中的对象被销毁前会做一些清理工作 一般而言&#xff0c;需要销毁的对象都应该做清理 解决方案 为每个类都提供一个public的free函数对象不在需要时立即调用free函数进行清理 存在的问题 free只是一个普通…

STM32H7的MPU学习和应用示例

STM32H7的MPU学习记录 什么是MPU&#xff1f;MPU的三种内存类型内存映射MPU保护区域以及优先级 MPU的寄存器XN位AP位TEX、C、B、S位SRD 位SIZE 位CTRL 寄存器的各个位 示例总结 什么是MPU&#xff1f; MPU&#xff08;Memory Protection Unit&#xff0c;内存保护单元&#xf…

文献分享:《基于中国人群的BRCA胚系突变筛查专家共识(2024年版)》

&#xff3b;摘要&#xff3d; BRCA基因&#xff08;包括BRCA1和BRCA2&#xff09;的胚系突变是家族性乳腺癌、卵巢癌等肿瘤的核心风险因素。在人群中&#xff0c;特别是已有肿瘤家族史的高危人群中&#xff0c;BRCA基因检测可以发挥预防性管理作用&#xff0c;有助于降低此类遗…

【C语言__编译和链接__复习篇2】

目录 前言 一、翻译环境和运行环境 二、翻译环境 2.1 预处理 2.1 编译 2.1.1 词法分析 2.1.2 语法分析 2.1.3 语义分析 2.2 汇编 2.3 链接 三、运行环境 四、简答主线问题 前言 本篇主要讨论以下问题&#xff1a; 主线问题&#xff1a; 1. 源文件(.c)如何转换成(.exe)文件…

每日一题---OJ题: 环形链表 II

片头 嗨! 小伙伴们,大家好! 我们又见面啦,在上一篇中,我们学习了环形链表I, 今天我们继续来打boss,准备好了吗? Ready Go ! ! ! emmm,同样都是环形链表,有什么不一样的地方呢? 肯定有, 要不然也不会一个标记为"简单" ,一个标记为"中等"了,哈哈哈哈哈 …

·13·1dawwd

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

轮腿机器人-五连杆正运动学解算

轮腿机器人-五连杆与VMC 1.五连杆正运动学分析2.参考文献 1.五连杆正运动学分析 如图所示为五连杆结构图&#xff0c;其中A&#xff0c;E为机器人腿部控制的两个电机&#xff0c;θ1,θ4可以通过电机的编码器测得。五连杆控制任务主要关注机构末端C点位置&#xff0c;其位置用直…

Zotero插件ZotCard中AI-NNDL文献笔记卡分享及卡片使用方法

一、卡片社区分享 github&#xff1a;ZotCard插件AI-NNDL论文卡片模板 Issue #67 018/zotcard (github.com) 二、卡片效果预览 ZotCard插件AI-NNDL论文卡片模板是关于人工智能神经网络与深度学习论文的笔记卡片&#xff0c;效果预览如下图&#xff1a; 三、卡片代码 经过了…

SAP CAP篇十七:写个ERP的会计系统吧,Part IV

本文目录 本系列文章目标开发步骤数据库表设计借贷初始化数据 会计凭证 Service 定义生成Fiori App更新CDS AnnotationApp运行 本系列文章 SAP CAP篇一: 快速创建一个Service&#xff0c;基于Java的实现 SAP CAP篇二&#xff1a;为Service加上数据库支持 SAP CAP篇三&#xff…

HarmonyOS分布式应用框架深入解读

随着越来越多设备的智能化&#xff0c;在多设备场景下应用开发面临以下挑战&#xff1a;从多设备的形态差异&#xff08;不同大小、不同分辨率、不同形状的屏幕&#xff0c;多样化的交互方式–按钮、触屏、键盘、语音、手势等&#xff09;&#xff0c;多设备的能力差异&#xf…

AI赋能档案开放审核:实战

关注我们 - 数字罗塞塔计划 - 为进一步推进档案开放审核工作提质增效&#xff0c;结合近几年的业务探索、研究及项目实践&#xff0c;形成了一套较为成熟、高效的AI辅助档案开放审核解决方案&#xff0c;即以“AI人工”的人机协同模式引领档案开放审机制创新&#xff0c;在档…

一站式开源持续测试平台 MerterSphere 之测试跟踪操作详解

一、MeterSphere平台介绍 MeterSphere是一站式的开源持续测试平台&#xff0c;遵循 GPL v3 开源许可协议&#xff0c;涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&#xff0c;全面兼容JMeter、Selenium 等主流开源标准&#xff0c;有效助力开发和测试团队充分利用云弹性…

TCP协议简单总结

TCP&#xff1a;传输控制协议 特点&#xff1a;面向连接、可靠通信 TCP的最终目的&#xff1a;要保证在不可靠的信道上实现可靠的传输 TCP主要有三个步骤实现可靠传输&#xff1a;三次握手建立连接&#xff0c;传输数据进行确认&#xff0c;四次挥手断开连接 三次握手建立可靠…

Golang ProtoBuf 初学者完整教程:语法

一、编码规范推荐 1、文件名使用小写下划线的命名风格&#xff0c;例如 lower_snake_case.proto 2、使用 2 个空格缩进 3、包名应该和目录结构对应 4、消息名使用首字母大写驼峰风格(CamelCase)&#xff0c;例如message StudentRequest { ... } 5、字段名使用小写下划线的风格…

【系统分析师】操作系统部分

文章目录 1、进程状态2、前趋图3、PV操作4、死锁问题5、存储管理5.1 页式存储5.2 段式存储5.3 段页式存储5.4 页面置换算法 6、文件管理6.1 索引文件结构6.2 空闲存储空间管理 7、设备管理7.1数据传输控制7.2 虚设备和SPOOLING技术7.3 微内核操作系统7.4 嵌入式操作系统 说明&a…

LeetCode-32. 最长有效括号【栈 字符串 动态规划】

LeetCode-32. 最长有效括号【栈 字符串 动态规划】 题目描述&#xff1a;解题思路一&#xff1a;辅助栈解题思路二&#xff1a;动态规划解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个只包含 ‘(’ 和 ‘)’ 的字符串&#xff0c;找出最长有效&#xff08;格式正确且…

「51媒体-邀约媒体」活动发布会新闻通稿如何写?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 撰写活动发布会的新闻通稿需要遵循一定的结构和内容要点&#xff0c;以确保信息的准确性、完整性和吸引力。以下是撰写活动发布会新闻通稿的基本步骤和建议&#xff1a; 标题&#xff1…

初识 QT

初始QT 什么是QTQT发展史QT支持的平台QT的优点QT的应用场景搭建QT开发环境QT的开发工具概述QT下载安装 使用QT创建项目QT 实现Hello World程序使用按钮控件来实现使用标签控件来实现 项目文件解析widget.hmain.cppwidget.cppwidget.ui.pro文件 对象树QT 窗口坐标体系 什么是QT …