摘要:随着前端应用的复杂度越来越高,如何管理应用的数据已经是一个不可回避的问题。应用的数据不是只有状态的,还有事件异步常量等等。出于以上两点原因,最终决定基于来设计一套管理应用的状态的解决方案。
随着前端应用的复杂度越来越高,如何管理应用的数据已经是一个不可回避的问题。当你面对的是业务场景复杂、需求变动频繁、各种应用数据互相关联依赖的大型前端应用时,你会如何去管理应用的状态数据呢?
我们认为应用的数据大体上可以分为四类:
事件:瞬间产生的数据,数据被消费后立即销毁,不存储。
异步:异步获取的数据;类似于事件,是瞬间数据,不存储。
状态:随着时间空间变化的数据,始终会存储一个当前值/最新值。
常量:固定不变的数据。
RxJS天生就适合编写异步和基于事件的程序,那么状态数据用什么去管理呢?还是用RxJS吗? 合不合适呢?
我们去调研和学习了前端社区已有的优秀的状态管理解决方案,也从一些大牛分享的关于用RxJS设计数据层的构想和实践中得到了启发:
使用RxJS完全可以实现诸如Redux,Mobx等管理状态数据的功能。
应用的数据不是只有状态的,还有事件、异步、常量等等。如果整个应用都由observable来表达,则可以借助RxJS基于序列且可响应的的特性,以流的方式自由地拼接和组合各种类型的数据,能够更优雅更高效地抽象出可复用可扩展的业务模型。
出于以上两点原因,最终决定基于RxJS来设计一套管理应用的状态的解决方案。
原理介绍对于状态的定义,通常认为状态需要满足以下3个条件:
是一个具有多个值的集合。
能够通过event或者action对值进行转换,从而得到新的值。
有“当前值”的概念,对外一般只暴露当前值,即最新值。
那么,RxJS适合用来管理状态数据吗?答案是肯定的!
首先,因为Observable本身就是多个值的推送集合,所以第一个条件是满足的!
其次,我们可以实现一个使用dispatch action模式来推送数据的observable来满足第二个条件!
众所周知,RxJS中的observable可以分为两种类型:
cold observable: 推送值的生产者(producer)来自observable内部。
将会推送几个值以及推送什么样的值已在observable创建时被定义下来,不可改变。
producer与观察者(observer) 是一对一的关系,即是单播的。
每当有observer订阅时,producer都会把预先定义好的若干个值依次推送给observer。
hot observable: 推送值的producer来自observable外部。
将会推送几个值、推送什么样的值以及何时推送在创建时都是未知的。
producer与observer是一对多的关系,即是多播的。
每当有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函数在内部调用了BehaviorSubject的next函数,推送了新数据,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处理数据你不需要关注同步还是异步。
下面的例子我们使用操作符from将promise转换为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,也许Redux和Mobx是更合适的选择。
这套基于RxJS的状态管理方案,我们已经用于开发公司的商用项目,反馈还不错。所以我们决定把这套方案整理成一个js lib,取名为:Floway,并在github上开源:
github源码:https://github.com/shayeLee/floway
使用文档:https://shayelee.github.io/floway
欢迎大家star,更欢迎大家来共同交流和分享RxJS的使用心得!
参考文章:
复杂单页应用的数据层设计
DaoCloud 基于 RxJS 的前端数据层实践
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/109340.html
摘要:前戏补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戏 2016/3/21 补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 ...
摘要:要求通过要求数据变更函数使用装饰或放在函数中,目的就是让状态的变更根据可预测性单向数据流。同一份数据需要响应到多个视图,且被多个视图进行变更需要维护全局状态,并在他们变动时响应到视图数据流变得复杂,组件本身已经无法驾驭。今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案。 前几篇文章在掘金首发基本石沉大海, 没什么阅读量. 可能是文章篇幅太长了?掘金值太低了? ...
摘要:技术积累经过社区的努力学习资料还是很多的,官方中文文档就已经很不错,不过我们先从天精通初步感受一下然后配合一些中文文档来补充知识点,最后再根据官方文档来校验整个知识体系。资料学习操作符的时候可以对照弹珠图的交互弹珠图的中文版中文文档 前言 最近准备毕设,技术选型的时候因为功能的一些需求准备将RxJs融入到项目中,考虑RxJs的时候因为之前的技术栈还犹豫了一下,查了一些资料以及粗略浏览了...
摘要:实现不定期更新技巧前端掘金技巧,偶尔更新。统一播放效果实现打字效果动画前端掘金前端开源项目周报前端掘金由出品的前端开源项目周报第四期来啦。 Web 推送技术 - 掘金腾讯云技术社区-掘金主页持续为大家呈现云计算技术文章,欢迎大家关注! 作者:villainthr 摘自 前端小吉米 伴随着今年 Google I/O 大会的召开,一个很火的概念--Progressive Web Apps ...
阅读 3075·2023-04-25 20:43
阅读 1718·2021-09-30 09:54
阅读 1589·2021-09-24 09:47
阅读 2874·2021-09-06 15:02
阅读 3509·2021-02-22 17:09
阅读 1232·2019-08-30 15:53
阅读 1440·2019-08-29 17:04
阅读 1955·2019-08-28 18:22