今天我們要做一個 RxJS 的經(jīng)典范例 - 自動完成 (Auto Complete)呈野,自動完成在實務(wù)上的應(yīng)用非常廣泛旗闽,幾乎隨處可見這樣的功能遵馆,只要是跟表單、搜尋相關(guān)的都會看到赦肃。
雖然是個很常見的功能鼻百,但多數(shù)的工程師都只是直接套套件來完成,很少有人會自己從頭到尾把完整的邏輯寫一次摆尝。如果有自己實作過這個功能的工程師,應(yīng)該就會知道這個功能在實作的過程中很多細(xì)節(jié)會讓程式碼變的非常複雜因悲,像是要如何取消上一次發(fā)送出去的 request堕汞、要如何優(yōu)化請求次數(shù)... 等等,這些小細(xì)節(jié)都會讓程式碼變的非常複雜且很難維護晃琳。
就讓我們一起來用 RxJS 來實作這個功能吧讯检!
需求分析
首先我們會有一個搜尋框(input#search),當(dāng)我們在上面打字并停頓超過 100 毫秒就發(fā)送 HTTP Request 來取得建議選項并顯示在收尋框下方(ul#suggest-list)卫旱,如果使用者在前一次發(fā)送的請求還沒有回來就打了下一個字人灼,此時前一個發(fā)送的請求就要捨棄掉,當(dāng)建議選項顯示之后可以用滑鼠點擊取建議選項代搜尋框的文字顾翼。
上面的敘述可以拆分成以下幾個步驟
- 準(zhǔn)備 input#search 以及 ul#suggest-list 的 HTML 與 CSS
- 在 input#search 輸入文字時投放,等待 100 毫秒再無輸入,就發(fā)送 HTTP Request
- 當(dāng) Response 還沒回來時适贸,使用者又輸入了下一個文字就捨棄前一次的并再發(fā)送一次新的 Request
- 接受到 Response 之后顯示建議選項
- 滑鼠點擊后取代 input#search 的文字
基本的 HTML 跟 CSS 筆者已經(jīng)幫大家完成灸芳,大家可以直接到下面的連結(jié)接著實作:
先讓我們看一下 HTML,首先在 HTML 裡有一個 input(#search)拜姿,這個 input(#search) 就是要用來輸入的欄位烙样,它下方有一個 ul(#suggest-list),則是放建議選項的地方
CSS 的部分可以不用看蕊肥,JS 的部分已經(jīng)寫好了要發(fā)送 API 的 url 跟方法getSuggestList
谒获,接著就開始實作自動完成的效果吧!
第一步壁却,取得需要的 DOM 物件
這裡我們會用到 #search 以及 #suggest-list 這兩個 DOM
const searchInput = document.getElementById('search');
const suggestList = document.getElementById('suggest-list');
第二步批狱,建立所需的 Observable
這裡我們要監(jiān)聽 收尋欄位的 input 事件,以及建議選項的點擊事件
const keyword = Rx.Observable.fromEvent(searchInput, 'input');
const selectItem = Rx.Observable.fromEvent(suggestList, 'click');
第三步展东,撰寫程式邏輯
每當(dāng)使用者輸入文字就要發(fā)送 HTTP request精耐,并且有新的值被輸入后就捨棄前一次發(fā)送的,所以這裡用 switchMap
keyword.switchMap(e => getSuggestList(e.target.value))
這裡我們先試著訂閱琅锻,看一下 API 會回傳什麼樣的資料
keyword
.switchMap(e => getSuggestList(e.target.value))
.subscribe(console.log)
在 search 欄位亂打幾個字
大家可以在 console 看到資料長相這樣卦停,他會回傳一個陣列帶有四個元素向胡,其中第一個元素是我們輸入的值,第二個元素才是我們要的建議選項清單惊完。
所以我們要取的是 response 陣列的第二的元素僵芹,用 switchMap 的第二個參數(shù)來選取我們要的
keyword
.switchMap(
e => getSuggestList(e.target.value),
(e, res) => res[1]
)
.subscribe(console.log)
這時再輸入文字就可以看到確實是我們要的返回值
寫一個 render 方法,把陣列轉(zhuǎn)成 li 并寫入 suggestList
const render = (suggestArr = []) => {
suggestList.innerHTML = suggestArr
.map(item => '<li>'+ item +'</li>')
.join('');
}
這時我們就可用 render 方法把取得的陣列傳入
const render = (suggestArr = []) => {
suggestList.innerHTML = suggestArr
.map(item => '<li>'+ item +'</li>')
.join('');
}
keyword
.switchMap(
e => getSuggestList(e.target.value),
(e, res) => res[1]
)
.subscribe(list => render(list))
如此一來我們打字就能看到結(jié)果出現(xiàn)在 input 下方了
只是目前還不能點選小槐,先讓我們來做點選的功能拇派,這裡點選的功能我們需要用到 delegation event 的小技巧,利用 ul 的 click 事件凿跳,來塞選是否點到了 li件豌,如下
selectItem
.filter(e => e.target.matches('li'))
上面我們利用 DOM 物件的 matches 方法(裡面的字串放 css 的 selector)來過濾出有點擊到 li 的事件,再用 map 轉(zhuǎn)出我們要的值并寫入 input控嗜。
selectItem
.filter(e => e.target.matches('li'))
.map(e => e.target.innerText)
.subscribe(text => searchInput.value = text)
現(xiàn)在我們就能點擊建議清單了茧彤,但是點擊后清單沒有消失,這裡我們要在點擊后重新 redner疆栏,所以把上面的程式碼改一下
selectItem
.filter(e => e.target.matches('li'))
.map(e => e.target.innerText)
.subscribe(text => {
searchInput.value = text;
render();
})
這樣一來我們就完成最基本的功能了曾掂,大家可以到這裡看初步的完成品。
還記得我們前面說每次打完字要等待 100 毫秒在發(fā)送 request 嗎壁顶? 這樣能避免過多的 request 發(fā)送珠洗,可以降低 server 的負(fù)載也會有比較好的使用者體驗,要做到這件事很簡單只要加上 debounceTime(100)
就完成了
keyword
.debounceTime(100)
.switchMap(
e => getSuggestList(e.target.value),
(e, res) => res[1]
)
.subscribe(list => render(list))
當(dāng)然這個數(shù)值可以依照需求或是請 UX 針對這個細(xì)節(jié)作調(diào)整若专。
這樣我們就完成所有功能了许蓖,大家可以到這裡查看結(jié)果。
今日小結(jié)
我們用了不到 30 行的程式碼就完成了 auto complete 的基本功能调衰,當(dāng)我們能夠自己從頭到尾的完成這樣的功能蛔糯,在面對各種不同的需求,我們就能很方便的針對需求作調(diào)整窖式,而不會受到套件的牽制蚁飒!比如說我們希望使用者打了 2 個字以上在發(fā)送 request,這時我們只要加上一行 filter 就可以了
keyword
.filter(e => e.target.value.length > 2)
.debounceTime(100)
.switchMap(
e => getSuggestList(e.target.value),
(e, res) => res[1]
)
.subscribe(list => render(list))
又或者網(wǎng)站的使用量很大萝喘,可能 API 在量大的時候會回傳失敗淮逻,主管希望可以在 API 失敗的時候重新嘗試 3 次,我們只要加個 retry(3)
就完成了
keyword
.filter(e => e.target.value.length > 2)
.debounceTime(100)
.switchMap(
e => Rx.Observable.from(getSuggestList(e.target.value))
.retry(3),
(e, res) => res[1]
)
.subscribe(list => render(list))
大家會發(fā)現(xiàn)我們的靈活度變的非常高阁簸,又同時兼顧了程式碼的可讀性爬早,短短的幾行程式碼就完成了一個複雜的需求,這就是 RxJS 的魅力啊~