找回密码
 会员注册
查看: 13|回复: 0

RxJS入门与实践_UTF_8

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
75139
发表于 2024-9-30 04:36:10 | 显示全部楼层 |阅读模式
作者:大力智能技术团队-前端 豆花饲养员写在最前面:(1) 刚入行时有过一年半Angular的开发经验,当时是做数据看板平台,需要处理看板和图表之间的复杂状态管理,而Angular内置的RxJS数据流管理配合依赖注入的强大特性完美cover住复杂数据流。(2) 在当下React技术栈开发过程中也使用到RxJS,主要用来做活动流程设计、基础组件数据流管理、跨层级组件通信等,使用数据驱动解耦逻辑。一、引言1.1 背景 编程范式命令式编程:通过语句或命令修改程序状态。(eg:JS、Java、Python)函数式编程:函数是第一等公民,通过函数调用、组合等完成复杂操作。(eg:科里化、函数组合)声明式编程:描述程序逻辑而非控制流。(eg:SQL、HTML、CSS)响应式编程:基于数据流及其变化传播的声明性编程范式。(eg:ReactUI = f(state))Rx (Reactive Extensions):响应式扩展 RxJS(Reactive Extensions for JavaScript):JS的响应式扩展RxJS能做什么?Think of RxJS as Lodash for events.将同步/异步操作统一抽象到流,关注数据流的变更,提供对数据流的一系列操作和处理方法。通过数据流驱动,有效降低代码逻辑耦合。1.2 前置概念 1.2.1 流In computer science, a stream is a sequence of data elements made available over time.A stream can be thought of as items on a conveyor belt being processed one at a time rather than in large batches.流是一系列随时间到达的数据。例如:事件流、直播数据流、文本编辑流、WebSocket。流可抹平同步和异步差异,异步和同步数据都可放入流中。1.2.2 观察者模式定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。优点抽象耦合,可实现UI层和数据层分离;抽象数据接口,定义稳定的消息传播机制;支持1对多广播通信缺点观察层级较深时,广播通信成本高;观察者和目标之间存在循环依赖时,将导致死循环崩溃;观察者仅感知目标变化,不感知其知变化过程二、核心概念2.1 Observable & Observer ObservableObservables are lazy Push collections of multiple values.Observables是多个值的懒推送集合。多个值:可推送多个值懒推送:在被第一次订阅时才推送值Pull vs PushPull: 数据消费者主动获取数据,数据生产方无感知。Push:数据生产方决定发送数据给消费者的时机,数据消费者无感知。单值多值PullFunctionIteratorPushPromiseObservableFunction 调用时同步返回单个值;Iterator 迭代式调用,调用时返回多个值;Promise 异步返回单个值;Observable 同步/异步地返回多个值。ObserverAn Observer is a consumer of values delivered by an Observable.Observer是Observable的数据消费者。Observer就是普通对象,类型定义如下:interfaceObserver{/**是否已关闭*/closed:boolean;/**流发出值时执行*/nextvalue:T)=>void;/**流出错时执行*/errorerr:any)=>void;/**流完成时执行*/complete)=>void;}//订阅流//observable.subscribe(observer);SubscriptionA Subscription is an object that represents a disposable resource, usually the execution of an Observable.订阅关系代表一个可关闭的资源(通常是Observable的执行)。//订阅流constsubscription=stream$.subscribe({nextval)=>console.info,error:console.error,complete)=>console.info('completed'),});//清理订阅subscription.unsubscribe();订阅完成后必须调用unsubscribe()以释放资源,防止内存泄露。2.2 Subject A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners.Subject是一种支持多播的Observable。既是Observable:可以直接订阅以获取数据也是Observer:可以调用next(v)、error(e)、complete()方法,将被广播至所有注册的Observer。Subject类型特点示意图Subject* 普通Subject * 支持多播BehaviorSubject* 随时间变化的数据流* 缓存当前值 * 被订阅时会立即推送最新值ReplaySubject* 缓存最近的若干值* 可设置缓存窗口AsyncSubject* 仅当完成时发出值2.3 Operator Operators are the essential pieces that allow complex asynchronous code to be easily composed in a declarative manner.操作符是声明式处理复杂异步的重要基石。 RxJS ships with operators. —— Ben LeshRxJS中操作符都是纯函数。官方提供的操作符基本上够日常开发使用,如果有特殊需求可以自定义操作符。管道操作符接收源Observable作为参数,返回新Observable。纯操作,即不改变源Observable。管道操作符类别参考。类别说明举例组合连接来自多个流的信息。基于值的顺序、时间和结构选择相应操作符。merge 合并流 concat 连接流转换基于源Observable的值转换为新Observable。map 转换流的值 switchMap 切换流过滤过滤源Observable的值。filter 过滤流的值 debounce 防抖多播单值流转换为多播流。multicast 使用Subject多播流错误处理处理流中的错误。retry 错误重试 catchError 捕获错误工具工具函数操作。tap 副作用(调试用) timestamp 打印流的值和时间戳条件对源Observable的条件处理。every 流每个值满足条件时返回true isEmpty 流是否为空聚集汇聚流的值得到新流。count 计数 reduce 类似数组reduce创建操作符用来创建Observable的函数,如of:按顺序发出任意数量的值interval:基于给定时间间隔发出数字序列fromEvent:将DOM事件转化为ObservablefromPromise:将Promise转化为Observable...2.4 Scheduler Scheduler controls when a subscription starts and when notifications are delivered.调度器控制订阅开始时机、流的数据传播时机。调度器提供:数据存储和消息队列调度订阅执行上下文虚拟时钟调度器类别在大部分场景下,使用默认调度器即可。类别说明举例null默认调度器同步、递归(常用)queueScheduler队列调度器同步、队列asapScheduler微任务调度器异步、微任务asyncScheduler异步调度器异步、宏任务animationFrameScheduler动画调度器window.requestAnimationFrame三、简单实践3.1 拖拽小球 StackBlitz项目rxjs-drag拖拽过程小球一次拖拽过程可描述为:鼠标按下(mousedown):拖拽开始,记录鼠标按下位置鼠标移动(mousemove):记录小球初始位置,在鼠标移动时更新小球位置鼠标弹起(mouseup):本次拖拽结束数据流拖拽过程中触发的鼠标事件对应三个流:鼠标按下mousedown$、鼠标移动mousemove$、鼠标弹起mouseup$。拖拽流drag$可描述如下:mousedown$触发拖拽,记录鼠标按下位置切换(switchMap)至mousemove$,记录小球初始位置,并在移动过程中计算小球移动位置当mouseup$触发时,终止(takeUntil)mousemove$constmousedown$=fromEvent(ball,'mousedown').pipe(map(getMouseEventPos));constmousemove$=fromEvent(document,'mousemove').pipe(map(getMouseEventPos));constmouseup$=fromEvent(document,'mouseup');constdrag$=mousedown$.pipe(switchMap(initialPos=>{const{top,left}=ball.getBoundingClientRect();returnmousemove$.pipe(map(({x,y})=>({top:y-initialPos.y+top,left:x-initialPos.x+left})),takeUntil(mouseup$));}));小球订阅drag$,更新自身位置。drag$.subscribe(({top,left})=>{ball.style.top=`${top}px`;ball.style.left=`${left}px`;ball.style.bottom='';ball.style.right='';});3.2 toast管理 StackBlitz项目rxjs-toasttoast需求一次toast可描述如下:(约定toast停留时长为3s,且不支持手动隐藏)展示一段文本3s内无新toast出现,自动隐藏3s内有新toast出现,重新计时3s,并覆盖老toast数据流将toast管理拆分为:展示show$:触发展示隐藏hide$:到达停留时长,或新toast覆盖当前constclick$=fromEvent(document.getElementById('btn'),'click');consttoast$=click$.pipe(switchMap(()=>{lethideByDuration=false;constduration$=timer(2000).pipe(mapTo('hidebyduration'),tap(()=>(hideByDuration=true)));returnconcat(of('show'),duration$).pipe(finalize(()=>{if(!hideByDuration){console.log('hidebynext');}}));}));toast$.subscribe(console.info);四、结语RxJS在流程控制、多个异步协作等场景中均表现良好,使用RxJS写出的代码简洁高效。希望大家在技术选型时可多多考虑RxJS。参考文档 ReactiveXRxJS官网学习RxJS操作符图解设计模式点击阅读原文,了解更多技术干货~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-15 21:41 , Processed in 0.664318 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表