Observable是一个集合了观察者模式、迭代器模式和函数式的库,提供了基于事件流的强大的异步处理能力,并且已在 Stage1草案中。介绍的 Rxjs是 Observable的一个实现,它是ReactiveX众多语言中的 JavaScript版本。

在 JavaScript中,我们可以使用 T|null去处理一个单值,使用 Iterator去处理多个值得情况,使用 Promise处理异步的单个值,而 Observable则填补了缺失的“异步多个值”。

使用 Rxjs

上文提到使用 EventEmitter做响应式处理,在 Rxjs中稍有些不同:

/*const change$ = new Subject();<Input change$={change$} /><Search change$={change$} />*/class Input extends Component {state = {value: ''};onChange = e => {this.props.change$.next(e.target.value);};componentDidMount() {this.subscription = this.props.change$.subscribe(value => {this.setState({value});});}componentWillUnmount() {this.subscription.ubsubscribe();}render() {const { value } = this.state;return <input value={value} onChange={this.onChange} />;}
}class Search extends Component {// ...componentDidMount() {this.subscription = this.props.change$.subscribe(value => {ajax(/* ... */).then(list =>this.setState({list}));});}componentWillUnmount() {this.subscription.ubsubscribe();}render() {const { list } = this.state;return <ul>{list.map(item => <li key={item.id}>{item.value}</li>)}</ul>;}
}
复制代码

在这里,我们虽然也需要手动释放对事件的订阅,但是得益于 Rxjs的设计,我们不需要像 EventEmitter那样去存下回调函数的实例,用于释放订阅,因此我们很容易就可以通过高阶组件解决这个问题。例如:

const withObservables = observables => ChildComponent => {return class extends Component {constructor(props) {super(props);this.subscriptions = {};this.state = {};Object.keys(observables).forEach(key => {this.subscriptions[key] = observables[key].subscribe(value => {this.setState({[key]: value});});});}onNext = (key, value) => {observables[key].next(value);};componentWillUnmount() {Object.keys(this.subscriptions).forEach(key => {this.subscriptions[key].unsubscribe();});}render() {return (<ChildComponent {...this.props} {...this.state} onNext={this.onNext} />);}};
};
复制代码

这样在需要聚合多个数据源时,也不会像 EventEmitter那样手动释放资源造成麻烦。同时,在 Rxjs中我们还有专用于聚合数据源的方法:

Observable.combineLatest(foo$, bar$).pipe(// ...);
复制代码

显然相对于 EventEmitter的方式十分高效,同时它相对于 Mobx也有巨大的优势。在 Mobx中,我们提到需要聚合多个数据源的时候,采用 autoRun的方式容易收集到不必要的依赖,使用 observe则不够高效。在 Rxjs中,显然不会有这些问题, combineLatest可以以很简练的方式声明需要聚合的数据源,同时,得益于 Rxjs设计,我们不需要像 Mobx一个一个去调用 observe返回的析构,只需要处理每一个 subscribe返回的 subscription:

class Foo extends Component {constructor(props) {super(props);this.subscription = Observable.combineLatest(foo$, bar$).pipe(// ...).subscribe(() => {// ...});}componentWillUnmount() {this.subscription.unsubscribe();}
}
复制代码

异步处理

Rxjs使用操作符去描述各种行为,每一个操作符会返回一个新的 Observable,我们可以对它进行后续的操作。例如,使用 map操作符就可以实现对数据的转换:

foo$.map(event => event.target.value);
复制代码

Rxjs5.5之后所有的 Observable上都引入了一个 pipe方法,接收若干个操作符, pipe方法会返回一个 Observable。因此,我们可以很容易配合 tree shaking实现对操作符的按需引入,而不是把整个 Rxjs引入进来:

import { map } from 'rxjs/operators';foo$.pipe(map(event => event.target.value));
复制代码

推荐使用这种写法。 在讨论面向对象的响应式的响应式中,我们提到对于异步的问题,面向对象的方式不好处理。在 Observable中我们可以通过 switchMap操作符处理异步问题,一个异步搜索看起来会是这样:

input$.pipe(switchMap(keyword => Observable.ajax(/* ... */)));
复制代码

在处理异步单值时,我们可以使用 Promise,而 Observable用于处理异步多个值,我们可以很容易把一个 Promise转成一个 Observable,从而复用已有的异步代码:

input$.pipe(switchMap(keyword => fromPromise(search(/* ... */))));
复制代码

switchMap接受一个返回 Observable的函数作为参数,下游的流就会切到这个返回的 Observable。 而要聚合多个数据源并做异步处理时:

combineLatest(foo).pipe( switchMap(keyword => fromPromise(someAsyncOperation(/* ... */))) ); 同时,由于标准制定的 Promise是没有 cancel方法的,有时候我们要取消异步方法的时候就有些麻烦(主要是为了解决一些并发安全问题)。 switchMap当上游有新值到来时,会忽略结束已有未完成的 Observable然后调用函数返回一个新的 Observable,我们只使用一个函数就解决了并发安全问题。当然,我们可以根据实际需要选用 switchMap、 mergeMap、 concatMap、 exhaustMap等。

而对于时间轴的操作, Rxjs也有巨大优势。上篇博客中提到当我们需要延时 5 秒做操作时,无论是 EventEmitter还是面向对象的方式都力不从心,而在 Rxjs中我们只需要一个 delay操作符即可解决问题:

input$.pipe(delay(5000) // 下游会在input$值到来后5秒才接到数据
);
复制代码

用 Rxjs 处理数据

在实际开发过程中,事件不能解决所有问题,我们往往会需要存储数据,而 Observable被设计成用于处理事件,因此它有很多符合事件直觉的设计。

Observable被设计为懒( lazy)的,当当没有订阅者时,一个流不会执行。对于事件而言,没有事件的消费者那么不执行也不会有问题。而在 GUI 中,订阅者可能是 View:

class View extends Component {state = {input: ''};componentDidMount() {this.subscription = input$.subscribe(input => {this.setState({input});});}componentWillUnmount() {this.subscription.unsubscribe();}render() {// ...}
}
复制代码

由于这个 View可能不存在,例如路由被切走了,那么我们的事件源就没有了订阅者,他就不会运行。但是我们希望在路由被且走后,后台的数据依然会继续。

对于事件而言,在事件发生之后的订阅者不会受到订阅之前的逻辑。例如在 EventEmitter中:

eventEmitter.emit('hello', 1);
// ...
eventEmitter.on('hello', function listener() {});
复制代码

由于 listener是在 hello事件发生后在监听的,不会收到值为 1的事件。但是这在处理数据的时候会造成麻烦,我们的数据在 View被卸载(例如路由切走)后丢失。

同时,由于 Observable没有提供直接取到内部状态的方法,当我们使用 Observable处理数据时,我们不方便随时拿到数据。那有办法解决这个问题,从而使 Observable强大抽象能力去赋能数据层呢?

回到 Redux。 Redux的事件(Action)其实是一个事件流,那么我们就可以很自然地把 Redux的事件流融入到 Rxjs流中:

() => next => {const action$ = new Subject();return action => {action$.next(action);// ...};
};
复制代码

通过这样的封装,redux-observable就能让我们把 Observable强大的事件描述和处理能力和 Redux结合。我们可以非常方便地根据 Action去处理副作用:

action$.pipe(ofType('ACTION_1'),switchMap(() => {// ...}),map(res => ({type: 'ACTION_2',payload: res}))
);action$.pipe(ofType('ACTION_3'),mergeMap(() => {// ...}),map(res => ({type: 'ACTION_4',payload: res}))
);
复制代码

ReduxObservable使我们可以结合 Redux和 Observable。在这里, Action被视作一个流, ofType相当于 filter(action=>action.type==='SOME_ACTION'),从而得到需要监听的 Action,得益于 Redux的设计,我们可以通过监听 Action去完成副作用的处理或者监听数据变化。最后这个流返回一个新的 Action流, ReduxObservable会把这个新的 Action流中的 Action dispatch出去。由此,我们在使用 Redux存储数据的基础上获得了 Rxjs对异步事件的强大处理能力。

浅谈前端响应式设计(二)相关推荐

  1. 浅谈html5 响应式布局

    一.什么是响应式布局? 响应式布局是Ethan Marcotte在2010年5月份提出的一个概念,简而言之,就是一个网站能够兼容多个终端--而不是为每个终端做一个特定的版本. 这个概念是为解决移动互联 ...

  2. 浅谈Spring5 响应式编程

    目录 为什么是响应式编程 用于响应式编程实现的理想案例 响应流 (Reactive Streams) Spring 5 提供的响应式编程 Spring Web Reactive vs. Spring ...

  3. 浅谈Vue响应式原理

    一.Vue中的响应式 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新. 二.响应式的基本原理 1.通过Objec ...

  4. 浅谈CSS3 响应式布局--Media Queries

    CSS3 Media Queries , CSS3媒体查询.使用 @media 查询,你可以针对不同的媒体类型定义不同的样式:也可以针对不同的屏幕尺寸设置不同的样式:当重置浏览器大小的过程中,页面也会 ...

  5. HTML学习笔记之类、布局、响应式设计(九)

    9.类.布局.响应式设计 9.1类 使用style标签设置样式颜色 div块儿级元素 <!--类 --> <html> <head> <style> . ...

  6. 前端学习13:HTML响应式设计、计算机代码、语义元素

    目录 HTML响应Web设计 一.什么是响应式Web设计? 1.流体网格 2.媒体查询 3.响应媒体 4.视口元标记 二.使用Bootstrap HTML计算机代码元素 HTML 语义元素 一.什么是 ...

  7. 使用前端框架Foundation 4来帮助简化响应式设计开发

    日期:2013-3-12  来源:GBin1.com Foundation是一套使用广泛的前端开发套件,可以帮助你快速的网站.最近ZURB发布了一个新版本的Foundation 4前端框架,能够有效的 ...

  8. 14个支持响应式设计的流行前端开发框架

    在几年前,并没有真正意义上的前端开发.随着网络技术的发展,网站和 Web 应用程序变得越来越复杂,前端部分的工作独立出来逐渐成为现在的前端开发.如今,我们可以看到越来越多的公司在招聘前端开发岗位. 前 ...

  9. 前端设计 响应式设计_响应设计简介

    前端设计 响应式设计 "Responsive Design" as a buzzword has reached peak popularity: we now have book ...

最新文章

  1. left join on 和where条件的放置
  2. 对于并列的TextField实现同步控制
  3. java 下对字符串的格式化
  4. 机器学习常见基本概念笔记
  5. flutter bloc_如何在Flutter中使用Streams,BLoC和SQLite
  6. 为什么说在国内考CISP比CISSP要好?
  7. 华为云举办AI经典论文复现活动,打造领先AI开发者学习社区
  8. squid 的配置详解 (转)--SeriesII
  9. 基本运算符中Swift和Java的比较
  10. 类和对象的关系练习题:需求:将汽车改装成3个轮子的车并换个颜色。
  11. 苹果审核技术支持URL导致的被拒解决方式
  12. 绅士游戏 android绅士在线阅读,一骑当千游戏,绅士游戏 android绅士
  13. netbeans开发php项目,NetBeans PHP 项目创建
  14. 不定时更新-JAVA干货博客
  15. 华罗庚 计算机,华罗庚有关计算机的故事
  16. 手工皮具的大坑之路-封边感悟与工具
  17. 第二次信奥考试试题及题解(部分)
  18. speedoffice(word)如何修改段落间距
  19. Sourcetree 跳过注册方法 for MAC
  20. react hooks(dva) echarts 实现中国地图省份数据分布显示

热门文章

  1. 90 条 Python 程序建议
  2. 利用zabbix自动发现监控mongo数据库
  3. 【Hive】多字符分隔
  4. jsp连接sqlServer数据库教程、jsp连接sqlServer数据库报ClassNotFoundException异常
  5. centos用ifconfig不显示ip地址的解决方法
  6. 如何从JavaScript中删除数组中的元素?
  7. 如何从命令行删除MongoDB数据库?
  8. win11安装报错0xc1900101怎么办 Windows11安装报错0xc1900101的解决方法
  9. ros中web端通过 按钮加载本地静态 pgm 地图显示在canvas画布中
  10. 运行控制器方法之前先执行注解@ModelAttribute的方法