30 天精通 RxJS(19): 實際示例 - 簡單 Auto Complete 實例

今天咱們要作一個 RxJS 的經典示例 - 自動完成 (Auto Complete),自動完成在開發上的應用很是普遍,幾乎隨處可見這樣的功能,只要是跟表單、搜尋相關的都會看到。javascript

雖然是個很常見的功能,但多數的工程師都只是直接套插件來完成,不多有人會本身從頭至尾把完整的邏輯寫一次。若是有本身寫過這個功能的工程師,應該就會知道這個功能在實現的過程當中不少細節會讓代碼變的很是複雜,像是要如何取消上一次發送出去的 request、要如何優化請求次數... 等等,這些小細節都會讓代碼變的很是複雜且很難維護。css

就讓咱們一塊兒來用 RxJS 來實現這個功能吧!java

需求分析

首先咱們會有一個搜索框(input#search),當咱們在上面打字並停頓超過 100 毫秒就發送 HTTP Request 來取得建議選項並顯示在收尋框下方(ul#suggest-list),若是使用者在前一次發送的請求尚未回來就打了下一個字,此時前一個發送的請求就要捨棄掉,當建議選項顯示以後能夠用滑鼠點擊取建議選項代搜索框的文字。數組

上面的敘述能夠拆分紅如下幾個步驟優化

  • 準備 input#search 以及 ul#suggest-list 的 HTML 與 CSS
  • 在 input#search 輸入文字時,等待 100 毫秒再無輸入,就發送 HTTP Request
  • 當 Response 還沒回來時,使用者又輸入了下一個文字就捨棄前一次的並再發送一次新的 Request
  • 接受到 Response 以後顯示建議選項
  • 滑鼠點擊後取代 input#search 的文字

基本的 HTML 跟 CSS 筆者已經幫你們完成,你們能夠直接到下面的連結接着實現:網站

先讓咱們看一下 HTML,首先在 HTML 裏有一個 input(#search),這個 input(#search) 就是要用來輸入的欄位,它下方有一個 ul(#suggest-list),則是放建議選項的地方ui

CSS 的部分能夠不用看,JS 的部分已經寫好了要發送 API 的 url 跟方法getSuggestList,接着就開始實現自動完成的效果吧!url

第一步,取得須要的 DOM 事件

這裏咱們會用到 #search 以及 #suggest-list 這兩個 DOMspa

const searchInput = document.getElementById('search');
const suggestList = document.getElementById('suggest-list');複製代碼

第二步,創建所需的 Observable

這裏咱們要監聽 搜索欄位的 input 事件,以及建議選項的點擊事件插件

const keyword = Rx.Observable.fromEvent(searchInput, 'input');
const selectItem = Rx.Observable.fromEvent(suggestList, 'click');複製代碼

第三步,撰寫代碼邏輯

每當使用者輸入文字就要發送 HTTP request,而且有新的值被輸入後就捨棄前一次發送的,因此這裏用 switchMap

keyword.switchMap(e => getSuggestList(e.target.value))複製代碼

這裏咱們先試着訂閱,看一下 API 會回傳什麼樣的資料

keyword
    .switchMap(e => getSuggestList(e.target.value))
    .subscribe(console.log)複製代碼

在 search 欄位亂打幾個字

你們能夠在 console 看到資料長相這樣,他會回傳一個數組帶有四個元素,其中第一個元素是咱們輸入的值,第二個元素纔是咱們要的建議選項清單。

因此咱們要取的是 response 數組的第二的元素,用 switchMap 的第二個參數來選取咱們要的

keyword
    .switchMap(
        e => getSuggestList(e.target.value),
        (e, res) => res[1]
    )
    .subscribe(console.log)複製代碼

這時再輸入文字就能夠看到確實是咱們要的返回值

寫一個 render 方法,把數組轉成 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))複製代碼

如此一來咱們打字就能看到結果出如今 input 下方了

只是目前還不能點選,先讓咱們來作點選的功能,這裏點選的功能咱們須要用到 delegation event 的小技巧,利用 ul 的 click 事件,來篩選是否點到了 li,以下

selectItem
  .filter(e => e.target.matches('li'))複製代碼

上面咱們利用 DOM 事件的 matches 方法(裏面的字串放 css 的 selector)來過濾出有點擊到 li 的事件,再用 map 轉出咱們要的值並寫入 input。

selectItem
  .filter(e => e.target.matches('li'))
  .map(e => e.target.innerText)
  .subscribe(text => searchInput.value = text)複製代碼

如今咱們就能點擊建議清單了,可是點擊後清單沒有消失,這裏咱們要在點擊後從新 redner,因此把上面的代碼改一下

selectItem
  .filter(e => e.target.matches('li'))
  .map(e => e.target.innerText)
  .subscribe(text => { 
      searchInput.value = text;
      render();
  })複製代碼

這樣一來咱們就完成最基本的功能了,你們能夠到這裏看初步的完成品。

還記得咱們前面說每次打完字要等待 100 毫秒在發送 request 嗎? 這樣能避免過多的 request 發送,能夠下降 server 的負載也會有比較好的使用者體驗,要作到這件事很簡單隻要加上 debounceTime(100) 就完成了

keyword
  .debounceTime(100)
  .switchMap(
    e => getSuggestList(e.target.value),
    (e, res) => res[1]
  )
  .subscribe(list => render(list))複製代碼

固然這個數值能夠依照需求或是請 UX 針對這個細節做調整。

這樣咱們就完成全部功能了,你們能夠到這裏查看結果。

今日小結

咱們用了不到 30 行的代碼就完成了 auto complete 的基本功能,當咱們可以本身從頭至尾的完成這樣的功能,在面對各類不一樣的需求,咱們就能很方便的針對需求做調整,而不會受到插件的限制!好比說咱們但願使用者打了 2 個字以上在發送 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))複製代碼

又或者網站的使用量很大,可能 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))複製代碼

你們會發現咱們的靈活度變的很是高,又同時兼顧了代碼的可讀性,短短的幾行代碼就完成了一個複雜的需求,這就是 RxJS 的魅力啊~

相關文章
相關標籤/搜索