若想 Input 輸入值就直接打 API,而不要透過 Button Click,傳統會寫在 input
Event,但若想每隔一段時間才取得 Input 值減少 API Request,也就是所謂 Debounce,自行要實作就沒那麼簡單,但透過 RxJS 可簡單實現。
Version
macOS Catalina 10.15.4
WebStorm 2020.1.2
Vue 2.6.11
RxJS 6.5.5
Browser
當 input 輸入後,會立即根據輸入值打 API 取得值顯示,不需透過 button click。
Event Model
<template>
<div>
<input type="text" @input="onInput">
{{ category }}
</div>
</template>
<script>
import axios from 'axios'
let fetchCategory = x => axios.get(`http://localhost:3000/categories/${x}`)
.then(x => x.data.value)
.catch(_ => 'N/A')
let onInput = function() {
let { target: { value: id }} = event
fetchCategory(id).then(
x => this.category = x
)
}
export default {
name:'App',
data: () => ({
category: ''
}),
methods: {
onInput
}
}
</script>
第 3 行
<input type="text" @input="onInput">
{{ category }}
傳統會使用 Event Model,將 input
event 使用 onInput()
處理,結果由 category
data 顯示。
15 行
let onInput = function() {
let { target: { value: id }} = event
fetchCategory(id).then(
x => this.category = x
)
}
由 event.target.value
取得輸入 id
,再呼叫 fetchCategory()
取得 category
,由於回傳是 Promise,因此必須使用 then()
寫入 category
side effect 使其在 HTML template 顯示。
這是典型 Vue 寫法,但若想每隔一段時間才取得 input 值,也就是要自行處理 debounce,這就沒這麼簡單了
Stream Model
<template>
<div>
<input type="text" v-stream:input="input$">
{{ category$ }}
</div>
</template>
<script>
import { of } from 'rxjs'
import { ajax } from 'rxjs/ajax'
import { pluck, debounceTime, exhaustMap, catchError } from 'rxjs/operators'
let fetchCategory$ = x => ajax(`http://localhost:3000/categories/${x}`).pipe(
pluck('response', 'category'),
catchError(_ => of('N/A'))
)
let subscriptions = function() {
let category$ = this.input$.pipe(
debounceTime(500),
pluck('event', 'target', 'value'),
exhaustMap(fetchCategory$)
)
return { category$ }
}
export default {
name:'App',
domStreams: ['input$'],
subscriptions
}
</script>
第 3 行
<input type="text" v-stream:input="input$">
{{ category$ }}
RxJS 則使用 Stream Model,將 input
event 直接轉成 input$
stream。
13 行
let fetchCategory$ = x => ajax(`http://localhost:3000/categories/${x}`).pipe(
pluck('response', 'category'),
catchError(_ => of('N/A'))
)
fetchCategory()
也由原本回傳 Promise 改成回傳 Observable。
19 行
let category$ = this.input$.pipe(
debounceTime(500),
pluck('event', 'target', 'value'),
exhaustMap(fetchCategory$)
)
RxJS 提供了 debounceTime()
operator,可直接對 input stream 做 debounce,由於 input stream 是 Observable,因此使用 exhaustMap()
讀取 API 避免 Observable of Observable。
直接回傳 category$
供 HTML template 顯示。
debounceTime()
每隔一段時間才過濾 Observable
Conclusion
- Debounce 是 RxJS 經典應用,若用 Event Model 將很難實現,但若改用 Stream Model 思考則相當直覺
Reference
Kartik Jagdale, RxJS: Reducing number of API Calls to your server using debounceTime()
RxJS, debounceTime()