前端的難點之一是讀取 API 時,回傳為 Promise,這是 Asynchronous 行為;若使用 RxJS 可視為 Observable,以 Stream 方式處理 API 回傳資料。
Version
macOS Catalina 10.15.4
WebStorm 2020.1
Vue 2.6.11
Vue-rx 6.2
RxJS 6.5.5
Browser
顯示 title
與 price
。
Data
[
{
"id": 1,
"title": "FP in JavaScript",
"price": 100,
"categoryId": 1
},
{
"id": 2,
"title": "RxJS in Action",
"price": 200,
"categoryId": 2
},
{
"id": 3,
"title": "Speaking JavaScript",
"price": 300,
"categoryId": 3
}
]
API 回傳為以上 array。
Promise
<template>
<div>
<ul>
<li v-for="(x, i) in books" :key="i">
Title : {{ x.title }}, Price : {{ x.price }}
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
let fetchBooks = axios.get('http://localhost:3000/books')
.then(x => x.data)
let mounted = function() {
fetchBooks.then(x => this.books = x)
}
export default {
name:'app',
data:() => ({
books:[],
}),
mounted,
}
</script>
14 行
let fetchBooks = axios.get('http://localhost:3000/books')
.then(x => x.data)
使用 Axios 回傳 Promise。
17 行
let mounted = function() {
fetchBooks.then(x => this.books = x)
}
在 mounted
hook 呼叫 fetchBooks()
,由於回傳的是 Promise,必須使用 then()
與 side effect 將資料寫進 books
data,才能顯示在 HTML template。
由於要寫進 data
,必須使用 this
,因此 mounted()
只能使用 function expression,而不能使用 arrow function。
第 4 行
<li v-for="(x, i) in books" :key="i">
Title : {{ x.title }}, Price : {{ x.price }}
</li>
v-for
directive 支援 books
data,可順利顯示在 HTML template。
Observable
<template>
<div>
<ul>
<li v-for="(x, i) in books$" :key="i">
Title : {{ x.title }}, Price : {{ x.price }}
</li>
</ul>
</div>
</template>
<script>
import { ajax } from 'rxjs/ajax'
import { pluck } from 'rxjs/operators'
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response')
)
let subscriptions = () => {
let books$ = fetchBooks$
return { books$ }
}
export default {
name:'app',
subscriptions,
}
</script>
15 行
let fetchBooks$ = ajax('http://localhost:3000/books').pipe(
pluck('response')
)
Axios 回傳 Promise,直接使用 RxJS 所提供的 ajax()
回傳 Observable。
實務上若 function 回傳 Observable,function 名稱會加上
$
postfix
pipe()
將多個 unary function 組合成新的 function
由於資料都放在 Promise 的 Response.data
下,而我們真正想要的資料又放在 books
下,因此使用 pluck()
從 data.books
擷取出想顯示的資料。
pluck()
從 object 中擷取指定 nested property
20 行
let books$ = fetchBooks$
定義 books$
Observable,由於 fetchBooks$
已經是 Observable,因此可直接指定。
22 行
return { books$ }
Vue 的 HTML template 並不支援 Promise,因此我們只能在 then()
以 side effect 寫入 data
,但卻支援 Observable,只要直接在 subscription()
回傳 Observable 即可。
第 4 行
<li v-for="(x, i) in books$" :key="i">
Title : {{ x.title }}, Price : {{ x.price }}
</li>
v-for
部分不變,唯從 books
data 改成 books$
Observable。
v-for
directive 也能順利支援 Observable。
Conclusion
- 使用 Promise 必須搭配
data
才能顯示在 HTML template,因此勢必使用this
- Observable 可直接在 HTML template 顯示,不須搭配
data
,也因此不用使用this
,可以讓 Vue 更接近 FP 風格,不用等 Vue 3 的 composition API 就可完全擺脫this
v-for
directive 完美支援 Observable,因此 HTML template 部分寫法不變
Reference
John Lindquist, Stream an API using RxJS into a Vue.js Template
RxJS, pipe()
RxJS, pluck()