热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

使用RxJS管理React应用状态的介绍

​本篇文章给大家带来的内容是关于使用RxJS管理React应用状态的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
本篇文章给大家带来的内容是关于使用RxJS管理React应用状态的介绍,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

随着前端应用的复杂度越来越高,如何管理应用的数据已经是一个不可回避的问题。当你面对的是业务场景复杂、需求变动频繁、各种应用数据互相关联依赖的大型前端应用时,你会如何去管理应用的状态数据呢?

我们认为应用的数据大体上可以分为四类:

  • 事件:瞬间产生的数据,数据被消费后立即销毁,不存储。
  • 异步:异步获取的数据;类似于事件,是瞬间数据,不存储。
  • 状态:随着时间空间变化的数据,始终会存储一个当前值/最新值。
  • 常量:固定不变的数据。

RxJS天生就适合编写异步和基于事件的程序,那么状态数据用什么去管理呢?还是用RxJS吗? 合不合适呢?

我们去调研和学习了前端社区已有的优秀的状态管理解决方案,也从一些大牛分享的关于用RxJS设计数据层的构想和实践中得到了启发:

  1. 使用RxJS完全可以实现诸如Redux,Mobx等管理状态数据的功能。
  2. 应用的数据不是只有状态的,还有事件、异步、常量等等。如果整个应用都由observable来表达,则可以借助RxJS基于序列且可响应的的特性,以流的方式自由地拼接和组合各种类型的数据,能够更优雅更高效地抽象出可复用可扩展的业务模型。

出于以上两点原因,最终决定基于RxJS来设计一套管理应用的状态的解决方案。

原理介绍

对于状态的定义,通常认为状态需要满足以下3个条件:

  1. 是一个具有多个值的集合。
  2. 能够通过event或者action对值进行转换,从而得到新的值。
  3. 有“当前值”的概念,对外一般只暴露当前值,即最新值。

那么,RxJS适合用来管理状态数据吗?答案是肯定的!

首先,因为Observable本身就是多个值的推送集合,所以第一个条件是满足的!

其次,我们可以实现一个使用dispatch action模式来推送数据的observable来满足第二个条件!

众所周知,RxJS中的observable可以分为两种类型:

cold observable: 推送值的生产者(producer)来自observable内部。

  • 将会推送几个值以及推送什么样的值已在observable创建时被定义下来,不可改变。
  • producer与观察者(observer) 是一对一的关系,即是单播的。
  • 每当有observer订阅时,producer都会把预先定义好的若干个值依次推送给observer

hot observable: 推送值的producer来自observable外部。

  • 将会推送几个值、推送什么样的值以及何时推送在创建时都是未知的。
  • producerobserver是一对多的关系,即是多播的。
  • 每当有observer订阅时,会将observer注册到观察者列表中,类似于其他库或语言中的addListener的工作方式。
  • 当外部的producer被触发或执行时,会将值同时推送给所有的observer;也就是说,所有的observer共享了hot observable推送的值。

RxJS提供的BehaviorSubject就是一种特殊的hot observable,它向外暴露了推送数据的接口next函数;并且有“当前值”的概念,它保存了发送给observer的最新值,当有新的观察者订阅时,会立即从BehaviorSubject那接收到“当前值”。

那么这说明使用BehaviorSubject来更新状态并保存状态的当前值是可行的,第三个条件也满足了。

简单实现

请看以下的代码:

import { BehaviorSubject } from 'rxjs';

// 数据推送的生产者
class StateMachine {
  constructor(subject, value) {
    this.subject = subject;
    this.value = value;
  }

  producer(action) {
    let oldValue = this.value;
    let newValue;
    switch (action.type) {
      case 'plus':
        newValue = ++oldValue;
        this.value = newValue;
        this.subject.next(newValue);
        break;
      case 'toDouble':
        newValue = oldValue * 2;
        this.value = newValue;
        this.subject.next(newValue);
        break;
    }
  }
}

const value = 1;  // 状态的初始值
const count$ = new BehaviorSubject(value);
const stateMachine = new StateMachine(count$, value);

// 派遣action
function dispatch(action) {
  stateMachine.producer(action);
}

count$.subscribe(val => {
  console.log(val);
});

setTimeout(() => {
  dispatch({
    type: "plus"
  });
}, 1000);

setTimeout(() => {
  dispatch({
    type: "toDouble"
  });
}, 2000);

执行代码控制台会打印出三个值:

Console

 1
 2
 4

上面的代码简单实现了一个简单管理状态的例子:

  • 状态的初始值: 1
  • 执行plus之后的状态值: 2
  • 执行toDouble之后的状态值: 4

实现方法挺简单的,就是使用BehaviorSubject来表达状态的当前值:

  • 第一步,通过调用dispatch函数使producer函数执行
  • 第二部,producer函数在内部调用了BehaviorSubjectnext函数,推送了新数据,BehaviorSubject的当前值更新了,也就是状态更新了。

不过写起来略微繁琐,我们对其进行了封装,优化后写法见下文。

使用操作符来创建状态数据

我们自定义了一个操作符state用来创建一个能够通过dispatch action模式推送新数据的BehaviorSubject,我们称她为stateObservable

const count$ = state({
  // 状态的唯一标识名称
  name: "count",
    
  // 状态的默认值
  defaultValue: 1,
    
  // 数据推送的生产者函数
  producer(next, value, action) {
    switch (action.type) {
      case "plus":
        next(value + 1);
        break;
      case "toDouble":
        next(value * 2);
        break;
    }
  }
});

更新状态

在你想要的任意位置使用函数dispatch派遣action即可更新状态!

dispatch("count", {
  type: "plus"
})

异步数据

RxJS的一大优势就在于能够统一同步和异步,使用observable处理数据你不需要关注同步还是异步。

下面的例子我们使用操作符frompromise转换为observable

指定observable作为状态的初始值(首次推送数据)

const todos$ = state({
  name: "todos",
    
  // `observable`推送的数据将作为状态的初始值
  initial: from(getAsyncData())
    
  //...
  
});

producer推送observable

const todos$ = state({
  name: "todos",
    
  defaultValue: []
    
  // 数据推送的生产者函数
  producer(next, value, action) {
    switch (action.type) {
      case "getAsyncData":
        next(
          from(getAsyncData())
        );
        break;
    }
  }
});

执行getAsyncData之后,from(getAsyncData())的推送数据将成为状态的最新值。

衍生状态

由于状态todos$是一个observable,所以可以很自然地使用RxJS操作符转换得到另一个新的observable。并且这个observable的推送来自todos$;也就是说只要todos$推送新数据,它也会推送;效果类似于Vue的计算属性。

// 未完成任务数量
const undoneCount$ = todos$.pipe(
  map(todos => {
    let _cOnut= 0;
    todos.forEach(item => {
      if (!item.check) ++_conut;
    });
    return _conut;
  })
);

React视图渲染

我们可能会在组件的生命周期内订阅observable得到数据渲染视图。

class Todos extends React.Component {
  componentWillMount() {
    todos$.subscribe(data => {
      this.setState({
        todos: data
      });
    });
  }
}

我们可以再优化下,利用高阶组件封装一个装饰器函数@subscription,顾名思义,就是为React组件订阅observable以响应推送数据的变化;它会将observable推送的数据转换为React组件的props

@subscription({
  todos: todos$
})
class TodoList extends React.Component {
  render() {
    return (
      

任务列表

{this.props.todos.map((item, n) => { return ; })}

); } }

总结

使用RxJS越久,越令人受益匪浅。

  • 因为它基于observable序列提供了较高层次的抽象,并且是观察者模式,可以尽可能地减少各组件各模块之间的耦合度,大大减轻了定位BUG和重构的负担。
  • 因为是基于observable序列来编写代码的,所以遇到复杂的业务场景,总能按照一定的顺序使用observable描述出来,代码的可读性很强。并且当需求变动时,我可能只需要调整下observable的顺序,或者加个操作符就行了。再也不必因为一个复杂的业务流程改动了,需要去改好几个地方的代码(而且还容易改出BUG,笑~)。

所以,以上基于RxJS的状态管理方案,对我们来说是一个必需品,因为我们项目中大量使用了RxJS,如果状态数据也是observable,对我们抽象可复用可扩展的业务模型是一个非常大的助力。当然了,如果你的项目中没有使用RxJS,也许ReduxMobx是更合适的选择。

这套基于RxJS的状态管理方案,我们已经用于开发公司的商用项目,反馈还不错。所以我们决定把这套方案整理成一个js lib,取名为:Floway,并在github上开源:

  • github源码:https://github.com/shayeLee/floway
  • 使用文档:https://shayelee.github.io/floway


推荐阅读
  • 打开文件管理器_【教程】模组管理器3.1食用指南
    文编:byakko最近有部分小伙伴反应还不会使用unity模组管理器,现在我就给大家讲一下unity模组管理器——从下载到使用。完整视频版以下是无WiF ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 学习笔记(34):第三阶段4.2.6:SpringCloud Config配置中心的应用与原理第三阶段4.2.6SpringCloud Config配置中心的应用与原理
    立即学习:https:edu.csdn.netcourseplay29983432482?utm_sourceblogtoedu配置中心得核心逻辑springcloudconfi ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • 本文介绍了求解gcdexgcd斐蜀定理的迭代法和递归法,并解释了exgcd的概念和应用。exgcd是指对于不完全为0的非负整数a和b,gcd(a,b)表示a和b的最大公约数,必然存在整数对x和y,使得gcd(a,b)=ax+by。此外,本文还给出了相应的代码示例。 ... [详细]
author-avatar
mobiledu2502926273
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有