中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

探秘vue-rx 2.0(推薦)

發布時間:2020-09-24 19:33:38 來源:腳本之家 閱讀:146 作者:xufei 欄目:web開發

前一段時間,我寫了兩篇文章,一篇是對目前前端主流視圖框架的思考,一篇是深入使用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>
  <h5>Single Value</h5>
  <div>{{single$}}</div>

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

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

  <h5>High-order</h5>
  <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>
  <h5>Watch</h5>
  <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這次的升級很好地滿足了我對復雜應用開發的需求了。

本文示例代碼參見:這里

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

工布江达县| 张北县| 于都县| 观塘区| 社会| 泸西县| 宁远县| 涡阳县| 延吉市| 周至县| 沛县| 三原县| 颍上县| 江口县| 城口县| 旅游| 阆中市| 新乐市| 乌拉特前旗| 南宫市| 罗源县| 凤翔县| 兴仁县| 蓝田县| 会东县| 隆昌县| 怀柔区| 东丽区| 波密县| 墨玉县| 武汉市| 虹口区| 陈巴尔虎旗| 弥渡县| 延津县| 大丰市| 蒲城县| 朝阳县| 恩平市| 开原市| 浪卡子县|