React状态管理方案盘点

news2024/12/23 13:49:57

您好, 如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想

前言

本文不会介绍各个状态管理工具的具体使用或者如何二次封装,而是从状态管理的概念入手,讲解我们应该关注状态管理工具的什么、常用状态管理工具的分类和比较、以及实际业务中如何去选择状态管理工具,让你对状态管理有更进一步的理解。

状态管理的概念

状态,是指数据的变化,而状态管理,也就是维护应用的数据变化。

它可以是视图状态,比如一个弹窗的visible、Tab的选中状态、激活的路由、是否有loading等;

也可以是逻辑状态,比如异步请求后端返回的数据(用户信息、列表内容等),或者是某个功能里各个状态之间的关系(比如文件上传这一功能中的上传进度、上传信息、是否断网、是否重连等)。

image.png 在react中,你可以使用useState或者this.state把状态维护在组件内部,通过props传递给子组件去使用。

如果是兄弟组件想要共享状态,也可以把状态抽离至公共父组件,然后同样通过props形式传递给子组件使用。

当两个组件离得比较远,但是又想共享状态,那么你可能就需要考虑用其他的方式来维护这些状态。这也就是引入状态管理要解决的一个问题:如何在组件间优雅的共享一些状态。

还有一种情况:当A组件的某个状态发生变化时,B组件的某个状态也需要跟着变化,可能是同步的变化,也可能是异步的变化。这是引入状态管理需要解决的另一个问题:如何 把各项状态之间的逻辑与其他系统模块之间的互动逻辑进行组织。


针对第二种情况,我们举一个典型的例子:

在逛某个App时,突然来了几条内部应用的通知,但是去消息页面查看时,却看不到那几条消息,需要手动刷新一下才能看见。这可能就是因为这个App的内部状态管理对于这两个状态的互动逻辑有所欠缺导致。

(我觉得掘金web端消息提醒也有类似的问题,被点赞后进去点赞消息页,提示的小红点并没消失,需要再点其他tab才消失,不知道是不是产品就想要这种效果)

状态管理工具的分类与比较

实现状态管理的方式:

  • Context API
  • redux
  • zustand
  • mobx
  • recoil
  • jotai
  • xState

状态管理方式特别多,我们关注的点可以聚焦在以下三个点:

  1. 状态管理器如何获取和设置基本的状态值,因为这是我们使用状态管理时最常做的两件事
  2. 状态管理器如何管理异步工作流,因为应用中可能做了很多异步工作
  3. 状态管理器是如何处理数据之间的联动(派生状态)

除此之外,我们还可以通过以下维度进行对比:

  1. 压缩前后的大小
  2. GitHub上的star数/npm每周下载量
  3. 社区活跃度(以Stack Overflow 上带标签的问题数为例)

这些属于数据类的比较,我们通过一个表格直观的来展示:

image.png

分类

在单独分析某一个状态管理工具之前,我们先根据其设计理念做一个分类:

  1. 单向数据流:redux、zustand
  2. 响应式:mobx
  3. 状态原子化:recoil、jotai

下面就挑Context、redux、mobx、recoil这几种状态管理方式讲一讲。

Context API

Context API并不是一个状态管理工具,他是React内置的状态管理功能。使用Context 的useContext+useReducer是基本可以实现redux的功能的,所以也是一种可行的状态管理方式。

如何获取和设置基本的状态值

Context API 用 useContext 生成一个useXxxContent,调用useXxxContent可以从其返回的对象拿到state和一个dispatch,你可以用这个dispatch来修改状态,也可以对dispatch做一个封装,实现一个可以修改state的函数。

const { state, dispatch } = useContext(StateContext);
// ...
dispatch({
  type: CHANGE_INPUT,
  inputValue: e.target.value,
});

如何处理异步

对于异步的逻辑,Context API并没有提供任何API,需要自己做封装。

如何处理数据间的联动

Context API并没有提供API来生成派生状态,同样也需要自行去封装一些方法来实现。

优点

  • 作为React内置的hook,不需要引入第三方库
  • 书写还算方便

缺点

  • Context 只能存储单一值,当数据量大起来时,你可能需要使用createContext创建大量context。
  • 直接使用的话,会有一定的性能问题:每一次对state的某个值变更,都会导致其他使用该state的组件re-render,即使没有使用该值。 你可以通过useMemo来解决这个问题,但是就需要一定的成本来定制一个通用的解决方案。

redux

redux是GitHub star数和周下载量都最多的状态管理工具。他的工作流程大致如下:

  • 用户在view层触发某个事件,通过dispatch发送了action和payload
  • action和payload被传入reducer函数,返回一个新的state
  • store拿到reducer返回的state并做更新,同时通知view层进行re-render

image.png 从上面的流程图我们可以看出,redux设计的思路就是单向数据流

除此之外,redux还遵循了以下原则:

  • 单一数据源。 redux的store只有一个,所有的状态都放在store中,所有的state共同组成了一个树形结构。
  • state是不可变的。 在redux中修改state的方式是dispatch一个action,根据action的payload返回一个新的state。
  • 纯函数修改。 redux通过reducer函数来修改状态,它接受前一次的state和action,返回新的state,只要传入相同的state和action,一定会返回相同的结果。

也就是这三个原则让redux的状态是可预测的。

如何获取和设置基本状态值

react-redux提供了两个API:useSelector、useDispatch来获取和设置状态,在函数式组件中设置/获取要比在class组件更方便。

如何处理异步

redux没有规定如何处理异步数据流,最原始的方式就是使用Action Creators,也就是在制造action之前进行各种的异步操作,你可以把要复用的操作抽离出来。

当然这样并不优雅,在实际项目中我们通常使用类似redux-thunk、redux-saga这些中间件来支持处理异步。

如何处理数据间联动

react-redux的useSelector获取状态后,你可以编写一些逻辑来处理派生状态。如果派生状态需要复用,记得给抽离出来。

优点

  • 繁荣的社区,像不支持异步这种问题是由成熟的中间件可以解决的,你遇到的问题多多少少可以在社区找到答案。
  • 可扩展性高,中间件模式让你可以随心所欲的武装你的dispatch。
  • 单一数据源且是树形结构,这让redux支持回溯,在调试上也更方便。

缺点

  • 大量的模版代码,写起来挺累人的,使用redux toolkit可以一定程度的减少。
  • 状态量大起来后,有可能会出现性能问题。要是啥玩意都往redux里存,可想而知,每次action过来把所有reducer跑一遍,多少有点噩梦的。当然redux后面开始支持拆分store,异步去加载store,没到这个业务的场景的时候不加载这个业务的store。但是如果业务耦合较为严重,那还是跑不掉。

mobx

mobx是一个非常典型的响应式状态管理工具。他的工作流程大致如下:

  • 用户在view层触发某个事件
  • 事件触发action执行,通过action来修改state
  • state更新后,computed Values也会根据依赖重新计算属性值
  • 状态更新后会触发reactions,来响应这次状态变化的一些操作(重新渲染组件、打印日志…)

image.png

mobx这种响应式的设计和vue很类似。与redux不同,mobx对全局state做了一层代理,监听state的变化,当state变化时,会自动更新相关的计算属性,所以mobx修改state是直接修改。

如何获取和设置基本的状态值

写完store后直接引入就可以:

const { xxxState, setXxxState } = xxxStore;

如何处理异步

mobx中的Actions指的是一段可以改变state的代码,他没有任何限制,对于修改state前的异步处理,放到Actions中即可,所以mobx对于异步的处理是很自然的。

如何处理数据间的联动

mobx提供了计算属性来处理派生状态,和vue的computed value很类似,基于state使用纯函数计算出另一个值。

优点

  • 上手简单。没有太多的概念和API,你只需要在store里声明你的state和修改state的方法就可以用了。

缺点

  • 风格自由。如果没有统一团队的代码风格,那你可能会在store中看到各种各样的代码。

Recoil

Recoil是React官方推出的一个状态管理库,设计的思路是将状态原子化。atom和selector是Recoil的两个核心。

atom:一个原子是一个共享状态的片段。

selector:一个组件可以订阅一个原子来获取/设置它的值。

网上的一张图很贴切:

image.png

每一个Atom是一个可订阅可修改的state单元。

如何获取和设置基本的状态值

要消费一个状态的时候,需要import两个东西:

import { useRecoilState } from "recoil";
import { xxxState } from "../store";

useRecoilState(xxxState);

如何处理异步

Recoil提供了一个useRecoilValueLoadable来处理异步操作。

首先:

const userInfo = selector({
  key: "userInfo",
  get: async () => {
    const res = await getUserInfo();
    return res.name;
  }
});

然后用useRecoilValueLoadable来消费:

const Info = () => {
  const userInfoLoadable = useRecoilValueLoadable(userInfo);
  switch (userInfoLoadable.state) {
    case "hasValue":
      // ...
    case "loading":
      // ...
    case "hasError":
      // ...
  }
};

如何处理数据间的联动

Recoil提供了selector来处理派生状态。比如我们的按钮是否可见跟随着tab的变化而变化:

import { CurrentTab } from './atom';
export const buttonVisible = selector({
  key: 'buttonVisible',
  get: ({ get }) => {
    const tab = get(CurrentTab);
    const buttonVisible = tab === 'A' ? true : false;
    return buttonVisible;
  },
});

优点

  • React官方推出的状态管理工具,有保障性。
  • 对 React concurrent 模式支持良好。

缺点

  • 不支持class组件。如果你是要更换项目的状态管理工具,应该看看原本项目中有没有用到class组件。
  • API比较多,上手稍微有点成本。
  • 消费一个状态比较繁琐,需要import两个东西。

如何选择状态管理工具

在选用或者更换状态管理工具之前,你可以询问自己:目前的状态管理方式,对于我的应用来说足够了吗?如果是更换状态管理工具,那么能解决什么问题,带来什么好处?

没有最好的状态管理方式,只有合适的状态管理方式。

根据上一小节对各个状态管理的比较,我们可以根据他们的优缺点来判断我们实际业务中到底选用哪一种方式。

  • 如果你的应用中,每个页面里各个部分的功能都比较独立,没有什么联动,那么你想要共享状态可能组件跨度并不大,或许就不需要状态管理器,提升状态至父组件然后使用props传递给子组件是一个选择。
  • 如果组件跨度较远,但是应用的需要共享的状态不多,想要共享的状态更新频率也不高,可以使用Context API解决。
  • 当然,从前面我们对context的分析可以看出,对于异步、派生状态、如何控制re-render等,我们都需要自己去封装一些通用的方法,所以当共享状态偏多,更新频率不低,我们就可以考虑使用状态管理工具来接管。
  • redux、mobx、recoil属于各有所长吧,无法直接说哪个更好,我做了些的归纳:
    • 如果组里有对于redux的一些封装和规范,那么redux会是一个好选择
    • 如果业务需要频繁更新状态(如在移动某个div时需要实时共享坐标),redux或许不太合适,mobx和recoil更为合适
    • 如果要共享的状态不算多,那么使用redux或许不太合适,mobx使用起来更为简单,recoil也可以试试
    • 如果组里有对于mobx的一些封装和规范,那么mobx会是一个好选择

当然还有一点很重要的,就是组里的人想用,用的顺手哈,毕竟状态管理工具是工具,用工具的是人,哪样顺手使用者说了算。

总结

  • 状态管理工具并不是必需品,有时候context API就够了。
  • 不要什么都存到状态管理工具里头去 !!!想清楚到底需不需要共享,有时候就是抽离到父组件的事。
  • 对于中大型项目,更重要的是制定代码规范,最好有相关的code review机制,要不然一段时间后你的状态管理就会像一团拉面一样(深有体会)

如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。

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

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

相关文章

自动驾驶系统激光雷达传感器反射率标定板

自动驾驶技术正在全球范围内快速发展和推广。在中国,自动驾驶技术也得到了高度重视和大力支持。中国政府已经出台了一系列政策,推动自动驾驶技术的发展和应用。例如,上海、北京等地已经开放了自动驾驶测试道路,并开展了自动驾驶公…

选择CRM系统主要看哪些指标?

很多企业都想选择一款好用的CRM客户管理系统,但是面对众多类型、品牌的CRM却犯了难。下面我们来说说,企业要想选到一款适合自己的、好用的CRM系统,主要看哪些指标?这里有6个步骤,可以帮您做到。 第1步:了解…

亚马逊鲲鹏系统六大优势

亚马逊鲲鹏系统六大优势凭借其独特的能力,完全模拟真实的人类行为。只需几个简单的步骤 就可以自由安排任务,让所有账户随时发挥最大的作用。 1、全自动化操作 可以全自动批量注册买家号、AI智能养号、全自动批量测评,模拟人类的操作行为例…

亚马逊鲲鹏系统能做什么

亚马逊鲲鹏系统是一款能绕过亚马逊智能检测,完全模拟人类真实行为,通过模拟真实的人流量来帮助你提升你的产品排名,让你的产品出现在搜索首页,从而快速提高你的销售业绩的营销工具! 主要的功能有批量注册买家号、AI智能…

微服务概念

微服务 微服务是什么 In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource A…

node插件MongoDB(四)—— 库mongoose 的文档操作使用

文章目录 前言(1)问题:安装的mongoose 库版本不应该过高导致的问题(2)重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 前言 &#…

新方向!文心一言X具身智能,用LLM大模型驱动智能小车

具身智能已成为近年来研究的热点领域之一。具身智能强调将智能体与实体环境相结合,通过智能体与环境的交互,来感知和理解世界,最终实现在真实环境中的自主决策和运动控制。 如何基于文心大模型,低成本入门“具身智能”&#xff0…

社区团购小程序系统源码+各种快递代收+社区便利店 带完整的搭建教程

社区团购小程序系统源码的开发背景可以追溯到近年来电商行业的快速发展,特别是在新冠疫情的影响下,线上购物在全球范围内得到了更广泛的普及和使用。社区团购作为一种新兴的电商模式,结合了传统团购和社交电商的优点,通过线上平台…

jQuery中淡入与淡出

在我们jQuery中为我们封装了很多好玩的方法,我为大家介绍一下淡入与淡出! 我们需要配合事件来玩淡入淡出 淡出语法:fadeOut([speed,[easing],[fn]) (1)参数都可以省略 (2)speed:三种预定速度之一的字符串(“slow”“normal”or “fast”)或…

QML8、布局元素

布局元素(Layout Items) QML使用anchors(锚)对元素进行布局。anchoring(锚定)是基础元素对象的基本属性,可以被所有的可视化QML元素使用。一个anchors(锚)就像一个协议,并且比几何变化更加强大。Anchors(锚)是相对关系的表达式,你通常需要与其它元素搭配使用。 一…

如何理解CDN?说说实现原理?

面试官:如何理解CDN?说说实现原理? 一、是什么 CDN (全称 Content Delivery Network),即内容分发网络 构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分…

Leo赠书活动-07期 【嵌入式虚拟化技术与应用】文末送书

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 赠书活动专栏 ✨特色专栏:…

Spring boot 整合grpc 运用

文章目录 GRPC基础概念:Protocol Buffers:proto 基础语法:调用类型: Spring boot 整合 grpc项目结构:整合代码:父 pomproto 模块服务端:客户端:实际调用: 原生集成 GRPC基…

振南技术干货集:C语言的一些“骚操作”及其深层理解(3)

注解目录 第二章《c语言的一些“操作”及其深层理解》 一、字符串的实质就是指针 (如何将 35 转为对应的十六进制字符串”0X23”?) 二 、转义符\ (打入字符串内部的“奸细”。) 三、字符串常量的连接 &#xff…

介绍YOLO-NAS Pose:姿势估计的技术

YOLO-NAS 姿势 YOLO-NAS Pose models是对 Pose Estimation 领域的最新贡献。今年早些时候,Deci 因其突破性的目标检测基础模型 YOLO-NAS 获得了广泛认可。在 YOLO-NAS 成功的基础上,该公司现在推出了 YOLO-NAS Pose 作为其姿势估计的对应产品。该姿势模型在延迟和准确性之间…

FinClip 产品10月报:官网新增PC终端麒麟版、UOS版下载

FinClip 的使命是使您(业务专家和开发人员)能够通过小程序解决关键业务流程挑战,并完成数字化转型的相关操作。不妨让我们看看在本月的产品与市场发布亮点,看看是否有助于您实现目标。 产品方面的相关动向👇&#x1f…

拆分代码 + 动态加载 + 预加载,减少首屏资源,提升首屏性能及应用体验

github 原文地址 我们看一些针对《如何提升应用首屏加载体验》的文章,提到的必不可少的措施,便是减少首屏幕加载资源的大小,而减少资源大小必然会想到按需加载措施。本文提到的便是一个基于webpack 插件与 react 组件实现的一套研发高度自定…

【java】JVM-关于Object o=new Object()

请解释一下对象的创建过程?(半初始化) 加问DCL要不要加volatile问题?(指令重排) 对象在内存中的存储布局?(对象与数组的存储不同) 例如一个class对象中有三个变量,分别是int(4bytes),long(8bytes)&#…

《QT从基础到进阶·十七》QCursor鼠标的不同位置坐标获取

一些常用鼠标图形: 鼠标光标相对于整个电脑屏幕的位置:QCursor::pos() 当前光标相对于当前窗口的位置:this->mapFromGlobal(QCursor::pos()) void MainWindow::mouseReleaseEvent(QMouseEvent* event) {QPoint pos event->pos(); …

学者观察 | 数字经济中长期发展中的区块链影响力——清华大学柴跃廷

导语 区块链是一种全新的分布式基础架构与计算范式,既能利用非对称加密和冗余分布存储实现信息不可篡改,又可以利用链式数据结构实现数据信息可溯源。当前,区块链技术已成为全球数据交易、金融结算、国际贸易、政务民生等领域的信息基础设施…