「2024」React 状态管理入门

news2024/12/23 9:29:21

概念

简单来说,状态指的是某一时刻应用中的数据或界面的呈现。这些数据可能包括用户填写表单的信息、应用内的用户偏好设置、应用的页面/路由状态、或者任何其他可能改变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.coloradmin.cn/o/1581190.html

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

相关文章

如何让MacOS「终端」走代理

在 MacOS 操作系统中&#xff0c;默认情况下&#xff0c;终端命令行不会通过代理进行网络连接。这导致在应用软件研发过程中&#xff0c;许多需要通过命令行下载安装的软件或依赖包无法成功安装。经常出现Failed to connect to xxx port 443 after 75329 ms: Couldnt connect t…

Window安装PostgresSQL

PostgreSQL 安装参考&#xff1a;Windows下安装PostgreSQL_window 安装postgresql-CSDN博客 安装好后打开pgAdmin4 配置Navicat连接PostgresSQL 找到安装目录文件 pg_hba.conf 修改配置增加&#xff1a; 修改前&#xff1a; # TYPE DATABASE USER ADDRES…

联想电脑开启虚拟化失败,开启虚拟化却提示还没有开启虚拟化

安装虚拟机的时候&#xff0c; 电脑要开启虚拟化&#xff0c; Intel VT&#xff0c; 去BIOS开启了&#xff0c; 但是依然报错&#xff0c;说虚拟化处于禁用状态。 解决方案&#xff1a; 去联想官方&#xff0c;下载BIOS更新包&#xff0c;更新BIOS。 更新文档&#xff1a; 联…

网络安全---Packet Tracer - 配置扩展 ACL

一、实验目的 在Windows环境下利用Cisco Packet Tracer进行 配置防火墙操作。 二、实验环境 1.Windows10、Cisco Packet Tracer 8.2 2.相关的环境设置 在最初的时候&#xff0c;我们已经得到了搭建好的拓扑模型&#xff0c;利用已经搭建好的拓扑模型&#xff0c;进行后续的…

3D Web轻量化引擎HOOPS Commuicator如何从整体装配中创建破碎的装配零件和XML?

前言 虽然可以从某些本机CAD格式&#xff08;其子组件驻留在单独的文件中&#xff0c;例如CATIA V5、Creo - Pro/E、NX或SolidWorks&#xff09;创建破碎装配&#xff0c;但无法从整体装配文件&#xff08;例如IFC、Revit&#xff09;创建或3DXML。 本文介绍了一个示例&#…

前端试题3#记录

1、vue3ts如果使用props参数要使用什么函数 interface Search {background : stringcolor : string } //defineProps 通过泛型参数来定义 props 的类型,给 props 设置默认值&#xff0c;需要使用 withDefaults 函数 const props withDefaults(defineProps<Search>(), {…

鹅厂实习offer

#转眼已经银四了&#xff0c;你收到offer了吗# 本来都打算四月再投实习了&#xff0c;突然三月初被wxg捞了&#xff08;一年前找日常实习投的简历就更新了下&#xff09;&#xff0c;直接冲了&#xff0c;流程持续二十多天&#xff0c;结果是运气还不错&#xff0c;应该是部门比…

【IO 模型】Unix IO 介绍

IO 模型&#xff08;一&#xff09; Unix IO 一个输入操作共包含两个阶段&#xff1a; 等待数据准备好从内核将数据复制到进程 对于一个套接字上的输入操作&#xff0c;通常第一步是等待数据从网络中到达&#xff0c;当数据到达时&#xff0c;先将数据复制到内核缓冲区中&a…

REST API实战演练之JavaScript使用Rest API

咱们前面讲了一下如何创建REST API 假期别闲着&#xff1a;REST API实战演练之创建Rest API-CSDN博客 又讲了java客户端如何使用REST API 假期别闲着&#xff1a;REST API实战演练之客户端使用Rest API-CSDN博客 接下来咱们看看JavaScript怎么使用REST API。 一、新建一个…

软件杯 深度学习人体跌倒检测 -yolo 机器视觉 opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的人体跌倒检测算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满…

(自用笔记)每天一点vue3——vue3+elementPlus+express获取excel数据并渲染到页面上

我是想做一个vue3echarts的账单数据展示项目&#xff0c;因为有vue2的基础&#xff0c;打算直接在这个项目上熟悉掌握vue3的新特性。这系列笔记就按照遇见问题解决问题的思路更新&#xff0c;不按照官方快速上手的章节&#xff0c;特此说明。 目标&#xff1a;利用nodejs搭建后…

GPT提示词分享 —— 中医

&#x1f449; 中医诊断涉及因素较多&#xff0c;治疗方案仅供参考&#xff0c;具体的方子需由医生提供。AI建议不能替代专业医疗意见&#xff0c;如果症状严重或持续&#xff0c;建议咨询专业医生。 我希望你能扮演一位既是老中医同时又是一个营养学专家&#xff0c;我讲描述…

gitlab、jenkins安装及使用文档一

gitlab-jenkins安装文档 IP地址操作系统服务版本192.168.75.137Rocky9.2jenkins 2.450-1.1 jdk 11.0.22 git 2.39.3192.168.75.138Rocky9.2gitlab-ce 16.10.0 gitlab安装 前期准备: 关闭防火墙及 SELinuxsystemctl disable --now firewalld sed -i s/^SELINUXenforcing$…

TCP 重传、滑动窗口、流量控制、拥塞控制(计算机网络)

重传机制 TCP 针对数据包丢失的情况&#xff0c;会用重传机制解决。 接下来说说常见的重传机制&#xff1a; 超时重传快速重传SACKD-SACK 超时重传 重传机制的其中一个方式&#xff0c;就是在发送数据时&#xff0c;设定一个定时器&#xff0c;当超过指定的时间后&#xff0c…

ComfyUI本地部署

一、部署准备 1. Comfyui下载 git clone https://github.com/comfyanonymous/ComfyUI.git2. 插件下载 辣椒酱的界面汉化&#xff1a; https://github.com/AIGODLIKE/AIGODLIKE-COMFYUI-TRANSLATION 提示词风格样式&#xff1a; https://github.com/twri/sdxl_prompt_styler …

RabbitMQ进阶——死信队列

RabbitMQ进阶——死信队列 什么是死信队列&#xff1f; 在消息队列中&#xff0c;执行异步任务时&#xff0c;通常是将消息生产者发布的消息存储在队列中&#xff0c;由消费者从队列中获取并处理这些消息。但是&#xff0c;在某些情况下&#xff0c;消息可能无法正常地被处理…

360加固脱壳实战

下载[]打开app使用frida -U -f com.xxx.xxx.xxx -l dupDex.js --no-pause<br> 命令 或者使用frida-hexdump -U -f com.inmo.inmolife命令在com.xxx.xxx.xxx 中寻找dex文件 或者在输出的日志中有输出路径使用dex2jar工具把dex文件转为jar文件 d2j-dex2jar.sh *.dex -d --…

为什么电脑越用越慢!

电脑随着时间推移逐渐变慢是一个常见的现象,其背后涉及多种原因。以下是导致电脑运行速度变慢的几个主要因素: 系统资源消耗增加 软件更新与新增应用:随着软件版本的更新和新应用程序的安装,它们往往对硬件资源的需求更高,尤其是对处理器、内存和硬盘的要求。这些新软件可…

深入理解Linux veth虚拟网络设备:原理、应用与在容器化架构中的重要性

在Linux网络虚拟化领域&#xff0c;虚拟以太网设备&#xff08;veth&#xff09;扮演着至关重要的角色&#x1f310;。veth是一种特殊类型的网络设备&#xff0c;它在Linux内核中以成对的形式存在&#xff0c;允许两个网络命名空间之间的通信&#x1f517;。这篇文章将从多个维…

N1922A是德科技N1922A功率传感器

181/2461/8938产品概述&#xff1a; N192XA 传感器是首款通过将直流参考源和开关电路集成到功率传感器中来提供内部调零和校准的传感器。此功能消除了与使用外部校准源相关的多个连接&#xff0c;从而最大限度地减少了连接器磨损、测试时间和测量不确定性。 连接到 DUT 时进行…