探秘vue-rx 2.0(推荐)


Posted in Javascript onSeptember 21, 2018

前一段时间,我写了两篇文章,一篇是对目前前端主流视图框架的思考,一篇是深入使用RxJS控制复杂业务逻辑的,在这两篇中,我分别提到:

  • 期望在复杂业务逻辑方面使用RxJS,更好地进行抽象,但是视图上使用轻量MVVM以达到快速开发的目的。
  • 目前VueJS中,如果要结合RxJS,可能需要手动订阅和取消订阅,写起来还是没有CycleJS方便。

最近,VueJS社区升级了vue-rx这个库,实现了比较方便地把VueJS和RxJS结合的能力。

我们来详细了解一下。

在视图上绑定Observable

VueJS本身不是基于RxJS这一套理念构建的,如果不借助任何辅助的东西,可能我们会需要干这么一些事情:

  • 手动订阅某些Observable,在observer里面,把数据设置到Vue的data上
  • 在视图销毁的时候,手动取消订阅

在业务开发中,我们最常用的是绑定简单的Observable,在vue-rx中,这个需求被很轻松地满足了。

与早期版本不同,vue-rx 2.0在Vue实例上添加了一个subscriptions属性,里面放置各种待绑定的Observable,用的时候类似data。

比如,我们可以这么用它:

rx-simple.vue

<template>
 <div>
  <h4>Single Value</h4>
  <div>{{single$}}</div>

  <h4>Array</h4>
  <ul>
   <li v-for="item of arr0$">{{item}}</li>
  </ul>
  <ul>
   <li v-for="item of arr1$">{{item}}</li>
  </ul>

  <h4>Interval</h4>
  <div>{{interval$}}</div>

  <h4>High-order</h4>
  <div>{{high$}}</div>
 </div>
</template>

<script>
import { Observable } from 'rxjs/Observable'

import 'rxjs/add/observable/of'

import 'rxjs/add/observable/from'
import 'rxjs/add/operator/toArray'

import 'rxjs/add/observable/interval'

import 'rxjs/add/observable/range'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/mergeAll'

const single$ = Observable.of(Math.PI)
const arr0$ = Observable.of([1, 1, 2, 3, 5, 8, 13])
const arr1$ = Observable.from([1, 1, 2, 3, 5, 8, 13]).toArray()
const interval$ = Observable.interval(1000)

const high$ = Observable.range(1, 5)
 .map(item => Observable.interval(item * 1000))
 .mergeAll()

export default {
 name: 'rx-simple',

 subscriptions: {
  single$,
  arr0$,
  arr1$,
  interval$,
  high$
 }
}
</script>

这个demo里面,演示了四种不同的Rx数据形态。其中,single$和interval$虽然创建方式不同,但实际上用的时候是一样的,因为,对它们的订阅,都是取其最后一个值,这两者的区别只是,一个不变了,一个持续变,但界面展示的始终是最后那个值。

关于数组,初学者需要稍微注意一下,从同样的数组,分别通过Observable.of和Observable.from出来的形态是大为不同的:

  • of创建的这个,里面只有一个值,这个值是个数组,所以,订阅它,会得到一个数组
  • from创建的这个,里面有若干个值,每个值是由数组中的元素创建的,订阅它,会一次性得到多个值,但展示的时候只会有最后一个,因为前面的都被覆盖掉了

那么,这个high$代表什么呢?

  • range操作,创建了一个流,里面有多个简单数字
  • map操作,把这个流升级为二阶,流里面每个元素又是一个流
  • mergeAll操作,把其中的每个流合并,降阶为一阶流,流里面每个元素是个简单数字

如果说不mergeAll,直接订阅map出来的那个二阶流,结果是不对的,vue-rx只支持一阶订阅绑定,不支持把高阶流直接绑定,如果有业务需要,应当自行降阶,通过各种flat、concat、merge操作,变成一阶流再进行绑定。

将Vue $watcher转换为Observable

上面我们述及的,都是从Observable的数据到Vue的ReactiveSetter和Getter中,这条路径的操作已经很简便了,我们只需把Observable放在vue实例的subscriptions里面,就能直接绑定到视图。

但是,反过来还有一条线,我们可能会需要根据某个数据的变化,让这个数据进入一个数据流,然后进行后续运算。

例如:有一个num属性,挂在data上,还有一个数据num1,表达:始终比num大1这么一件事。

当然,我们是可以直接利用computed property去做这件事的,为了使得我们这个例子更有说服力,给它这个加一计算添加一个延时3秒,强行变成异步:始终在num属性确定之后,等3秒,把自己变成比num大1的数字。

这样,computed property就写不出来了,我们可能就要手动去$watch这个num,然后在回调方法中,去延时加一,然后回来赋值给num1。

在vur-rx中,提供了一个从$watch创建Observable的方法,叫做$watchAsObservable,我们来看看怎么用:

rx-watcher.vue

<template>
 <div>
  <h4>Watch</h4>
  <div>
   <button v-on:click="num++">add</button>
   source: {{num}} -> result: {{num$}}
  </div>
 </div>
</template>

<script>
import 'rxjs/add/operator/pluck'
import 'rxjs/add/operator/startWith'
import 'rxjs/add/operator/delay'

export default {
 name: 'rx-watch',

 data() {
  return {
   num: 1
  }
 },

 subscriptions() {
  return {
   num$: this.$watchAsObservable('num')
    .pluck('newValue')
    .startWith(this.num)
    .map(a => a + 1)
    .delay(3000)
  }
 }
}
</script>

这个例子里面的num$经过这么几步:

  • this.$watchAsObservable('num'),把num属性的变动,映射到一个数据流上
  • 这个数据流的结果是一个对象,里面有newValue和oldValue属性,我们通常情况下,要的都是newValue,所以用pluck把它挑出来
  • 注意,这个检测的只是后续变动,对于已经存在的值,是$watch不到的,所以,用startWith,把当前值放进去
  • 然后是常规的rx运算了

那么,这件事的原理是什么呢?

我们知道,Vue实例中,data上的属性都会存在ReactiveSetter,所以它被赋值的时候,就会触发这个setter,所以,$watchAsObservable的内部只需根据数据变动,生成一个Observable就可以了。

$watchAsObservable的方法签名如下:

$watchAsObservable(expOrFn, [options])

这个options,跟vue的$watch方法的options一样。

有时候,我们会有这样的情况:在组件实例化的时候,数据流由于缺少某些条件,可能还没法创建。

比如说,某个组件,依赖于路由上面的某个参数,这时候,可能你不知道怎么去初始化绑定。

其实,产生这样的想法,本身就错了,因为没有用Rx的理念去思考问题。想一下下面这句话:

数据流的定义,与初始条件是否具备无关。

初始条件其实也只是整个数据流管道中的一节,如果初始不确定的话,我们只要给它留一个数据入口就好了,后续的流转定义可以全部写得出来。

const taskId$ = new Subject()
const task$ = taskId$
 .distinctUntilChanged()
 .switchMap(id => this.getInitialData(id))

然后,在路由变更等事件里,往这个taskId$里面next当前的id就可以了。通过这种方式,我们就可以把task$直接绑定到界面上。

或者,taskId$也可以通过在路由上面的watch转化而成,只是不能直接用$watchAsObservable,可以考虑改进一下这种情况。

这样可以实现组件canReuse的情况下,改动路由参数,触发当前页面的数据刷新,实现视图的更轻量级的刷新。

将DOM事件转化为Observable

使用RxJS可以直接把DOM事件转化为Observable,vue-rx也提供了一个类似的方法来做这个事,不过我没理解这两个东西有什么差异?具体参见官方示例吧。

构建优化

关注vue-rx的readme,可以发现,目前推荐使用绑定的方式是这样:

import Vue from 'vue'
import Rx from 'rxjs/Rx'
import VueRx from 'vue-rx'

// tada!
Vue.use(VueRx, Rx)

但这样会有一个问题,import的是rxjs/Rx,我们看到,这个文件里把所有可以被挂接到Rx对象上的东西都import进来了,这会导致构建的时候没法tree-shaking,用不到的那些操作符也被构建进来了,一个简单的demo,可能构建结果也有200多k,这还是太大了。

我们查看一下vue-rx的源码,发现传入的这个Rx是怎么使用的呢?

var obs$ = Rx.Observable.create(function (observer) {

...

// Returns function which disconnects the $watch expression
var disposable
if (Rx.Subscription) { // Rx5
 disposable = new Rx.Subscription(unwatch)
} else { // Rx4
 disposable = Rx.Disposable.create(unwatch)
}

这里,其实只是要使用Observable和Subscription这两个东西,所以我们可以改成这样:

import Vue from 'vue'
import { Observable } from 'rxjs/Observable'
import { Subscription } from 'rxjs/Subscription'
import VueRx from 'vue-rx'

// tada!
Vue.use(VueRx, { Observable, Subscription })

再试试,构建大小只有不到100k了,而且是可以正常运行的。如果用的是Rx 4,需要传入的就是Disposable而不是Subscription。

另外,如果我们使用了$watchAsObservable,还会需要引入另外一个东西:

import 'rxjs/add/operator/publish'

这是因为在$watchAsObservable里面,为了共享Observable,把它pubish之后refCount了,所以要引入,用不到这个方法的话,可以不引。

如果使用了$fromDOMEvent,还需要引入这个:

import 'rxjs/add/observable/empty'

因为$fromDOMEvent里面的这段:

if (typeof window === 'undefined') {
 return Rx.Observable.empty()
}

小结

有了这个库之后,我们就可以比较优雅地结合VueJS和RxJS了。之前,两者之间结合的麻烦点主要在于:

在RxJS体系中,数据的进、出这两头是有些繁琐的。

所以,CycleJS采用了比较极端的做法,把DOM体系也包括进去了,这样,编写代码的时候,数据就没有进出的成本,但这么做,其实是牺牲了一些视图层的编写效率。

而Angular2中,用的是async这个pipe来解决这问题,这也是一种比较方便的办法,在绑定Observable这一点上,跟有了vue-rx之后的Vue是差不多简便的。

React体系里面也有对RxJS的适配,而且还有跟Redux,Mobx对接的适配,感兴趣的可以自行关注。

从个人角度出发,vue-rx这次的升级很好地满足了我对复杂应用开发的需求了。

本文示例代码参见:这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js自动生成对象的属性示例代码
Oct 28 Javascript
Jquery Post处理后不进入回调的原因及解决方法
Jul 15 Javascript
页面内容排序插件jSort使用方法
Oct 10 Javascript
JavaScript 不支持 indexof 该如何解决
Mar 30 Javascript
ionic实现带字的toggle滑动组件
Aug 27 Javascript
js创建对象几种方式的优缺点对比
Sep 28 Javascript
深入探究angular2 UI组件之primeNG用法
Jul 26 Javascript
IntersectionObserver实现图片懒加载的示例
Sep 29 Javascript
AngularJS表单验证功能
Oct 19 Javascript
元素全屏的设置与监听实例
Nov 28 Javascript
JQuery Ajax动态加载Table数据的实例讲解
Aug 09 jQuery
从0到1构建vueSSR项目之node以及vue-cli3的配置
Mar 07 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
Sep 21 #Javascript
vue+axios实现文件下载及vue中使用axios的实例
Sep 21 #Javascript
vue-rx的初步使用教程
Sep 21 #Javascript
基于Vue 服务端Cookies删除的问题
Sep 21 #Javascript
Vue中插入HTML代码的方法
Sep 21 #Javascript
node.js调用C++函数的方法示例
Sep 21 #Javascript
Vue中Quill富文本编辑器的使用教程
Sep 21 #Javascript
You might like
测试您的 PHP 水平的题目
2007/05/30 PHP
一步一步学习PHP(8) php 数组
2010/03/05 PHP
php插入中文到sqlserver 2008里出现乱码的解决办法分享
2012/07/19 PHP
php操作redis缓存方法分享
2015/06/03 PHP
PHP微信分享开发详解
2017/01/14 PHP
jquery easyui combox一些实用的小方法
2013/12/25 Javascript
学习JavaScript设计模式之观察者模式
2020/04/22 Javascript
jQuery实现鼠标选中文字后弹出提示窗口效果【附demo源码】
2016/09/05 Javascript
针对后台列表table拖拽比较实用的jquery拖动排序
2016/10/10 Javascript
JS定时器实现数值从0到10来回变化
2016/12/09 Javascript
angularJS之$http:与服务器交互示例
2017/03/17 Javascript
JavaScript你不知道的一些数组方法
2017/08/18 Javascript
详解在vue-cli项目中使用mockjs(请求数据删除数据)
2017/10/23 Javascript
electron demo项目npm install安装失败的解决方法
2018/02/06 Javascript
使用vue-router为每个路由配置各自的title
2018/07/30 Javascript
在vue项目中,将juery设置为全局变量的方法
2018/09/25 Javascript
vue 返回上一页,页面样式错乱的解决
2019/11/14 Javascript
微信小程序 scroll-view 实现锚点跳转功能
2019/12/12 Javascript
Vue中父子组件的值传递与方法传递
2020/09/28 Javascript
JavaScript实现网页留言板功能
2020/11/23 Javascript
python实现感知器
2017/12/19 Python
对Python的交互模式和直接运行.py文件的区别详解
2019/06/29 Python
Python循环结构的应用场景详解
2019/07/11 Python
Christys’ Hats官网:英国帽子制造商
2018/11/28 全球购物
Myprotein俄罗斯官网:欧洲第一运动营养品牌
2019/05/05 全球购物
生产厂长岗位职责
2014/02/21 职场文书
护理专科毕业自荐信范文
2014/04/21 职场文书
领导干部民主生活会自我剖析材料范文
2014/09/20 职场文书
音乐教师个人工作总结
2015/02/06 职场文书
风雨哈佛路观后感
2015/06/03 职场文书
2016大学军训心得体会
2016/01/11 职场文书
《黄山奇石》教学反思
2016/02/18 职场文书
《追风筝的人》:人心中的成见是座大山,但请不忘初心
2019/11/15 职场文书
SQL实现LeetCode(176.第二高薪水)
2021/08/04 MySQL
springboot 多数据源配置不生效遇到的坑及解决
2021/11/17 Java/Android
VUE使用draggable实现组件拖拽
2022/04/06 Vue.js