點燈坊

失くすものさえない今が強くなるチャンスよ

使用 watchAsObservable() 將 Data 變化視為 Observable

Sam Xiao's Avatar 2020-05-17

Data Binding 是 Vue 的一大特色,但我們也可使用 watchAsObservable() 將 data 轉成 Observable 以 RxJS 處理。

Version

macOS Catalina 10.15.4
WebStorm 2020.1
Vue 2.6.11
Vue-rx 6.2
RxJS 6.5.5

watchAsObservable()

watch000

<input> 的 Number 變化,下方會以 +1 立即改變。

<template>
  <div>
    <input type="number" v-model="value">
    <h1>{{ sum$ }}</h1>
  </div>
</template>

<script>
import { pluck, map } from 'rxjs/operators'

let subscriptions = function() {
  let sum$ = this.$watchAsObservable('value').pipe(
    pluck('newValue'),
    map(x => +x + 1)
  )

  return { sum$ }
}

export default {
  name: 'app',
  data:() => ({
    value: 0
  }),
  subscriptions,
}
</script>

第 3 行

<input type="number" v-model="value">
<h1>{{ sum$ }}</h1>

<input>v-model 綁定到 value,下方則顯示 sum$ Observable。

12 行

let sum$ = this.$watchAsObservable('value').pipe(
  pluck('newValue'),
  map(x => +x + 1)
)

使用 $watchObservable() 將 data 轉成 Observable,由於其包含 newValueoldvalue 兩個 property,因此使用 pluck()newValue 取得目前 value,並使用 map() 改變 Observable 內部值。

value 經過 <input type="number"> 的 data binding 後,其 type 成為 String,故使用 + 使其轉型成 Number。

map()
直接改變 Observable 內部值

watch001

immediate: true

watch002

上例會發現 sum$ 沒資料顯示,若要一開始就顯示 1 呢 ?

<template>
  <div>
    <input type="number" v-model="value">
    <h1>{{ sum$ }}</h1>
  </div>
</template>

<script>
import { pluck, map } from 'rxjs/operators'

let subscriptions = function() {
  let sum$ = this.$watchAsObservable('value', { immediate: true }).pipe(
    pluck('newValue'),
    map(x => +x + 1)
  )

  return { sum$ }
}

export default {
  name: 'app',
  data:() => ({
    value: 0
  }),
  subscriptions,
}
</script>

12 行

let sum$ = this.$watchAsObservable('value', { immediate: true }).pipe(
  pluck('newValue'),
  map(x => +x + 1)
)

$watchAsObservable() 的第二個 argument 傳入 { immediate: true },則 sum$ 一開始就有 1 可顯示。

Side Effect

watch003

若希望 sum$ 也能同時有 side effect 呢 ?

<template>
  <div>
    <input type="number" v-model="value">
    <h1>{{ sum$ }}</h1>
  </div>
</template>

<script>
import { from } from 'rxjs'
import { pluck, map } from 'rxjs/operators'

let subscriptions = function() {
  let sum$ = this.$watchAsObservable('value', { immediate: true }).pipe(
    pluck('newValue'),
    map(x => +x + 1)
  )

  from(sum$).subscribe(x => console.log(x))

  return { sum$ }
}

export default {
  name: 'app',
  data:() => ({
    value: 0
  }),
  subscriptions,
}
</script>

18 行

from(sum$).subscribe(x => console.log(x))

原本的 $sum() 要用於顯示,故無法直接使用 subscribe(),因此使用 from() 另外建立新的 Observable 用於 subscribe() 處理 side effect。

Conclusion

  • 除了 DOM event 與 API 可視為 Observable,data 的變化也可視為 Observable
  • Observable 的 map() 其實與 Maybe 的 map() 意義相同,可視為改變 Observable 內部值
  • Computed 與 watch 也有 reactive programming 概念,因此本範例也能使用 computed 與 watch 實現,但別忘了 RxJS 具有豐富的 operator,FRP 是 Vue 所有沒有的

Reference

John Lindquist, Watch Vue.js v-models as Observable with $watchAsObservable and RxJS
Vue-rx, $watchAsObservable()
RxJS, pluck()
RxJS, map()