RxJS 难在哪里?

一、开始之前

本文适合初学 RxJS 者,有项目实践,有理论说明

二、项目示例: 使用 RxJS 打通前后端数据流

实践优先,以下基于 RxJS + Vite + 原生 JSExpress全栈项目

1、初始化项目

sh 复制代码
pnpx create vite rx-start-handle # 选择原生 js

pnpm add rxjs cors # 可能会遇到跨域问题

2、基于 RxJScount 的加减操作

说明
前端 展示数据/发起请求
后端 数据传输/计算

3、Express 后端:三个 GET 接口

基于 RxJS 模拟异步操作使用 Promise + RxJS,模拟数据库异步操作:

路由 说明
/init 初始化数据
/add 数据 + n
/dec 数据 - n
js 复制代码
import express from 'express'
import cors from 'cors'
import { from, map } from 'rxjs'

const app = express();

let state = 0;

app.use(cors())

function op(action, timeout = 300, n = 1) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(action === 'add') {
        state += n
        resolve(state)
      } else if (action === 'dec') {
        state -= n
        resolve(state)
      } else {
        resolve(state)
      }
      
    }, timeout)
  })
}

app.get('/init', (req, res) => {
  from(op('', 50, 1)).subscribe({
    next: (v) => {
      res.json({
        code: 0,
        message: 'success',
        data: `${v}`
      });
    }
  })
})

app.get('/add', (req, res) => {
  from(op('add', 50, 1)).subscribe({
    next: (v) => {
      res.json({
        code: 0,
        message: 'success',
        data: `${v}`
      });
    }
  })
})

app.get('/dec', (req, res) => {
  from(op('dec', 50, 1)).subscribe({
    next: (v) => {
      res.json({
        code: 0,
        message: 'success',
        data: `${v}`
      });
    }
  })
})

app.listen(3000, () => {
  console.log('port: http://localhost:3000')
})

模拟数据异步操作,不使用多播,不使用 Subject 主题。

4、对初始化的 vite 初始化的项目进行微调

jsx 复制代码
import './styles/style.css'
import javascriptLogo from './assets/javascript.svg'
import viteLogo from '/vite.svg'

// js
import { handle } from './counter.js'

document.querySelector('#app').innerHTML = `
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="${viteLogo}" class="logo" alt="Vite logo" />
    </a>
    <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" target="_blank">
      <img src="${javascriptLogo}" class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Vite!</h1>
    <div class="card">
      <div id="result"></div>
      <button id="add" type="button">server +</button>
      <button id="dec" type="button">server -</button>
    </div>
    <p class="read-the-docs">
      Click on the Vite logo to learn more
    </p>
  </div>
`

5、使用 RxJS 给按钮绑定可观察事件,并用函数式处理逻辑

ts 复制代码
import { fromEvent, switchMap, catchError } from 'rxjs'
import { addApi$, decApi$, initApi$ } from './api/request'


export function handle() {
  const resultDiv = document.getElementById('result');
  
  const setResultDivContent = (response) => {
    if(response && response.code === 0) {
      resultDiv.textContent = response.data
    } else {
      alert("更新数据失败")
    }
  }

  initApi$.subscribe({
    next: (response) => {
      setResultDivContent(response?.response)
    },
    error: () => { },
    complete: () => {
      console.log("completed!")
    }
  })

  const addClick$ = fromEvent(document.querySelector('#add'), 'click')

  addClick$.pipe(
    switchMap(() => addApi$),
    catchError(error => {
      console.error('An error occurred:', error);
      return [];
    })
  ).subscribe({
    next: (response) => {
      setResultDivContent(response?.response)
    },
    error: () => { },
    complete: () => {
      console.log("completed!")
    }
  })

  const decClick$ = fromEvent(document.querySelector('#dec'), 'click')

  decClick$.pipe(
    switchMap(() => decApi$),
    catchError(error => {
      console.error('An error occurred:', error);
      return [];
    })
  ).subscribe({
    next: (response) => {
      setResultDivContent(response?.response)
    },
    error: () => { },
    complete: () => {
      console.log("completed!")
    }
  })
}

在按钮上定义可观察对象,并通过 pipe 进行处理数据,使用 subscribe 订阅数据变化。我们看到以上的代码中,函数逻辑巨多,这是 RxJS 编程范式的特点(函数式编程))。

6、效果

三、为什么 RxJS 看似简单,但实际上手难?

  • 编程范式(函数、响应与常规的框架和库有较大区别。例如:在可观察对象中,DOM 事件 能与 定时器 使用函数方式进行组合。)
  • RxJS 可以理解为 迭代器发布订阅模式 的结合体(实现数据,和多推数据。)
  • 巨多的函数 操作符 需要理解与实践, 可观察对象自身的组合情况是繁多。

1、 如何破解难度?

  • 确定对 RxJS 的需求度,一般的业务其实使用常规的 JS 和框架就够完成任务了。
  • 确定需要 RxJS, 找到自己的合适的学习方式,不断的练习,可以在理论上归纳总结。
  • 在不同的平台 Node.js、浏览器平台进行测试,
  • 使用测试方式验证测试用例。
  • 使用断点调试,调试源码。

四、RxJS 理论基础

| 名字 | 说明 |
|-----------------------------|-----------|---------------------------|
| Observer Pattern | 观察者模式 | 观察者和可观察对象,主题 |
| Observable | 可观察对象 | 可观察对象表示一个异步的数据流或事件流 |
| Observer | 观察对象 | 用于监听 Observable 对象的变化 |
| Operators | 操作符 | 对可观察对象的进行操作,如果变换、映射、过滤等操作 |
| Subscription | 订阅 | 订阅用于管理观察者和可观察对象之间的连接 |
| Data Stream Control | 数据流控制 | 控制数据的流程频率等操作 |
| Error Handling | 错误处理 | 对异常进行捕获和重试 |
| Asynchronous Operations | 异步操作 | HTTP 请求、定时器 |
| Parallel Operations | 并行操作 | 将多个可观察对象合并成一个 |
| Memory Management | 内存管理 | 取消订阅,避免内存泄露 |

六、创建可观察对象的不同方式

创建可观察对象的不同方式 说明
使用构造函数 new 关键字
从操作符函数创建 ajax/of/from/interval/...
从操作符组合创建 combineLatest/concat/...

提示: 如何掌握如此之多的操作符?不断练习,找到常用操祖符,不断的练习,常用的 100% 会用。

七、异步特性

  • RxJS 可以通过 bindCallback 操作符方便的将 回调函数形式 转换成 可观察对象的形式
  • RxJS 可以通过 from 操作符方便的将 Promise 转换成 可观察对象的形式

以下是一些常见的 RxJS 异步操作符,以表格形式进行总结:

操作符 描述 示例
debounceTime 延迟发出值,只在停止输入一段时间后才发出 debounceTime(300)
throttleTime 在一段时间内只发出第一个值 throttleTime(300)
delay 延迟发出值 delay(1000)
timeout 设置等待时间,超时后发出错误 timeout(5000)

八、从可观察对象到主题

前面已经提到了可观察对象,其实是 一对一 方式进行传播数据。基于观察者模式,能够扩展到 一对多, 与从 可观察对象主题 就出现了。

主题是一种特殊的 可观察对象,主题自己实现了 next/complete/error 方法,自己能订阅和消费。但是在 RxJS 中主题根据不同的功能,可以分为以下四种:

主题种类名 说明
Subject 基本的主题,允许多个观察者订阅,并且可以通过调用 next() 方法来手动发出新值
BehaviorSubject 当一个观察者订阅它时,它会立即发出最新的值,然后继续发出后续的值。它需要一个初始值作为参数。
ReplaySubject 会在被订阅时"回放"先前的多个值给观察者,可以指定回放的数量。
AsyncSubject Observable 完成时,只发出最后一个值给观察者。如果 Observable 没有完成,它将不会发出任何值。

九、项目适合 rxjs 吗?

  • 普通项目不用 RxJS 就可以解决大部分问题。
  • RxJS 提供强大的可观察对象的,操作符,处理管道中数据的能力,明显适合复杂的项目,统一项目数据管理,例如在 AngularNestJS 中就内置了 RxJS
  • 需要一段时间的学习过程,并且到熟练程度难度相对还比较高,因为相当于重新熟练一种编程范式。
类型 说明
复杂异步 RxJS 有自己异步处理方式,如果异步非常复杂,可以考虑使用 RxJS
复杂的状态管理 如果你的代码里面复杂的状态,并与异步一起结合,也可以考虑使用 RxJS
实时同步 使用 websocket 应用程序的具有实时特性的应用程序,可以考虑使用 RxJS
其他 ...

十、仅仅将 RxJs 作为统一数据层?

  • 将不同的数据来源,统一使用 RxJS 可观察对象进行处理。
    • HTTP 请求
    • WebSocket
    • 用户输入
  • 数据变换
    • 变换过滤映射等操作
  • 状态管理
    • 依托强大的数据操作能力,做状态里游刃有余
  • 组件通信
    • Subjects 充当中间介质,跨域组件通信
  • 异步操作
    • RxJS 支持异步并发,功能强大,容易组合
  • 错误处理
    • RxJS 的错误处理机制可以更好地处理异步操作可能出现的错误,从而使应用程序更加健壮

十一、小结

本基于 RxJSVite 的原生 JS + Express 的前后端作为实践的开始,然后讲解了 RxJS 的基本原理,通过更多的练习加强 RxJS 的能力,掌握 RxJS 需要大量不同场景的实践。RxJS 构建一套自己编程方式。同时分析 RxJS 是否适合自己的项目,以及是否适合作为统一的数据层处理数据。希望这篇文章能帮助阅读者。

相关推荐
cc蒲公英12 分钟前
vue2中使用vue-office库预览pdf /docx/excel文件
前端·vue.js
Sam902924 分钟前
【Webpack--013】SourceMap源码映射设置
前端·webpack·node.js
Python私教1 小时前
Go语言现代web开发15 Mutex 互斥锁
开发语言·前端·golang
A阳俊yi1 小时前
Vue(13)——router-link
前端·javascript·vue.js
小明说Java1 小时前
Vue3祖孙组件通信探秘:运用provide与inject实现跨层级数据传递
前端
好看资源平台1 小时前
前端框架对比与选择:如何在现代Web开发中做出最佳决策
前端·前端框架
4triumph1 小时前
Vue.js教程笔记
前端·vue.js
程序员大金2 小时前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
清灵xmf2 小时前
提前解锁 Vue 3.5 的新特性
前端·javascript·vue.js·vue3.5
程序员大金2 小时前
基于SpringBoot的旅游管理系统
java·vue.js·spring boot·后端·mysql·spring·旅游