「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.rhkb.cn/news/311368.html

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

相关文章

【DA-CLIP】图像退化类型检测功能演示代码

背景 在CLIP基础上微调而来&#xff0c;使用图像控制器编码生成退化类型embedding并在训练中对图像编码器进行控制。针对十种退化类型进行了训练。 解决CLIP模型在图像纹理等层面无法针对退化类型识别或识别率较低的问题。 训练数据集情况 GitHub有对应数据集连接 完整代码 项…

HBase的数据模型与架构

官方文档&#xff1a;Apache HBase – Apache HBase™ Homehttps://hbase.apache.org/ 一、HBase概述 1.概述 HBase的技术源自Google的BigTable论文&#xff0c;HBase建立在Hadoop之上&#xff0c;是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;用于…

工作流引擎项目解析

API 编辑 在Camunda中&#xff0c;API的继承关系主要体现在各个服务接口之间。以下是Camunda中一些常见服务接口的继承关系&#xff1a; ProcessEngineServices 接口&#xff1a; RepositoryService&#xff1a; 负责管理流程定义和部署。 RuntimeService&#xff1a; 负责管…

2024年nodejs调用小红书最新关注(粉丝)follow接口,api接口分析2004-04-16

一、打开chrome按f12&#xff0c;点击右上角的“关注”按钮&#xff0c;抓包位置如下&#xff1a; (图1 follow接口) 二、follow接口分析 1、请求地址 https://edith.xiaohongshu.com/api/sns/web/v1/user/follow 2、请求方法: POST 3、请求头&#xff1a; :authority: edith…

最新AI创作系统ChatGPT网站源码AI绘画,GPTs,AI换脸支持,GPT联网提问、DALL-E3文生图

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

25. 【Android教程】列表控件 ListView

在学习了 ScrollView 及 Adapter 两节内容之后&#xff0c;大家应该对 ListView 有了一些基本的了解&#xff0c;它是一个列表样式的 ViewGroup&#xff0c;将若干 item 按行排列。ListView 是一个很基本的控件也是 Android 中最重要的控件之一。它可以帮助我们完成多个 View 的…

Udio——革命性的AI音乐生成软件

Udio是一款革命性的AI音乐生成软件&#xff0c;由前谷歌DeepMind的顶尖AI研究人员和工程师共同创立&#xff0c;得到著名风险投资公司a16z的支持。它旨在为音乐爱好者和专业人士提供一个全新的音乐创作和分享平台。用户可以通过文本提示来生成音乐&#xff0c;支持广泛的音乐风…

水牛社:打造你的居家副业利器,让赚钱变得更简单

水牛社&#xff0c;这个已经陪伴我们走过九个春秋的综合性网络任务和项目资源整合平台&#xff0c;真的给我留下了深刻的印象。它不仅仅是一个资源的汇聚地&#xff0c;更是一个帮助无数人实现网络副业梦想的平台。作为一个资深用户&#xff0c;我深感其中的价值和魅力。 水牛…

“香港批准比特币、以太坊ETF”!华夏、博时、嘉实计划发行相关产品!美国ETF分析师泼冷水:别指望香港ETF会有很大流量!

周一(4月15日)&#xff0c;比特币短线迎来反弹行情&#xff0c;币价回升至66000美元上方。华夏基金、博时国际与嘉实投资3家中国头部基金宣布&#xff0c;旗下虚拟资产现货ETF获准在香港发行。 据了解&#xff0c;华夏基金&#xff08;香港&#xff09;现计划发行能够投资于现货…

两部电话机怎样能实现对讲?直接连接能互相通话吗?门卫门房传达室岗亭电话怎么搞?

目录 两部电话机能直接连接吗&#xff1f;用三通头分出来一条电话线两部电话机用一根电话线直接连接能互相通话吗&#xff1f; 什么电话机可以直接连接两部IP电话机&#xff08;网络电话机&#xff09;可以直接连接两部普通电话机之间通过一个电话交换机也可以连接跨区域的两部…

mfc 带有复选框的ListBox

mfc 带有复选框的 ListBox 效果&#xff1a; 添加 ListBox 控件 从工具箱拖拽 ListBox 控件到窗口上&#xff0c;并设置属性&#xff1a; 包含字符串&#xff1a;true所有者描述&#xff1a;Fixed 给ListBox添加控制变量 添加完后&#xff0c;将m_list_box的类型使用CC…

react使用npm i @reduxjs/toolkit react-redux

npm i reduxjs/toolkit react-redux 创建一个 store文件夹&#xff0c;里面创建index.js文件和子模块文件夹 index,js文件写入以下代码 import {configureStore} from reduxjs/toolkit // 导入子模块 import counterReducer from ./modules/one import two from ./modules/tw…

OpenBayes 在线教程|张国荣、鲁迅等老照片秒变高清!即刻上手的超火 SUPIR-AI 图像修复教程

小伙伴们&#xff0c;大家在生活中是不是也会遇到这样的烦恼&#xff1a;心心念念想要打印一张充满回忆的老照片或酷炫动漫壁纸&#xff0c;却发现图像糊得像打了马赛克&#xff1f; 市面上的图像修复工具五花八门&#xff0c;选择困难症人群找得快要崩溃&#xff1f; 终于找…

Linux: softirq 简介

文章目录 1. 前言2. softirq 实现2.1 softirq 初始化2.1.1 注册各类 softirq 处理接口2.1.2 创建 softirq 处理线程 2.2 softirq 的 触发 和 处理2.1.1 softirq 触发2.1.2 softirq 处理2.1.2.1 在 中断上下文 处理 softirq2.1.2.2 在 ksoftirqd 内核线程上下文 处理 softirq 3.…

电子烟特效音语音方案选型-WTN6020-8S-E

随着科技的迅猛进步&#xff0c;电子烟行业亦在持续创新与突破&#xff0c;引领着全新的潮流。其中&#xff0c;电子烟产品所特有的吸烟声音特效播报功能&#xff0c;无疑成为了技术革新的璀璨亮点。这一设计巧妙地将吸烟的声效融入使用体验中&#xff0c;使得用户在吸电子烟时…

CSS3 立体 3D 变换

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍CSS3 立体 3D 变换&#x1f48e;1 坐标轴&#x1f48e;2 perspective 透视视…

linux管理进程

一、程序 程序&#xff1a;执行特定任务的一串代码 1.是一组计算机能识别和执行的指令&#xff0c;运行于电子计算机上&#xff0c;满足人们某种需求的信息化工具 2.用于描述进程要完成的功能&#xff0c;是控制进程执行的指令集 二、进程和线程 1.进程 进程是程序的执行…

vue2 二次封装element 组件,继承组件原属性,事件,插槽 示例

测试页面代码 这里主要记录如何封装element的el-input 并且封装后具有el-input原本的属性 事件 插槽 下面为测试页面即组件调用 <script> import CustomInput from /components/CustomInput.vue;export default {name: TestPage,components: { CustomInput },data() …

jenkins构建微信小程序并展示二维码

测试小程序的过程中&#xff0c;很多都是在回头和前端开发说一句&#xff0c;兄弟帮我打一个测试版本的测试码&#xff0c;开发有时间的情况下还好&#xff0c;就直接协助了&#xff0c;但是很多时候他们只修复了其中几个bug&#xff0c;其他需要修复的bug代码正在编写&#xf…

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都已更新完毕&#xff0c;欢迎大家前往订阅本专题&#x1f38f; &#x1f38f;【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 &#x1f38f;【蓝桥杯嵌入式】蓝桥…