30 天精通 RxJS(19): 實務(wù)范例 - 簡易 Auto Complete 實作

今天我們要做一個 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)建議選項顯示之后可以用滑鼠點擊取建議選項代搜尋框的文字顾翼。

image.png

上面的敘述可以拆分成以下幾個步驟

  • 準(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 欄位亂打幾個字

image.png

大家可以在 console 看到資料長相這樣卦停,他會回傳一個陣列帶有四個元素向胡,其中第一個元素是我們輸入的值,第二個元素才是我們要的建議選項清單惊完。

所以我們要取的是 response 陣列的第二的元素僵芹,用 switchMap 的第二個參數(shù)來選取我們要的

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

這時再輸入文字就可以看到確實是我們要的返回值

image.png

寫一個 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 下方了

image.png

只是目前還不能點選小槐,先讓我們來做點選的功能拇派,這裡點選的功能我們需要用到 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 的魅力啊~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末启妹,一起剝皮案震驚了整個濱河市筛严,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饶米,老刑警劉巖桨啃,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件车胡,死亡現(xiàn)場離奇詭異,居然都是意外死亡照瘾,警方通過查閱死者的電腦和手機匈棘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來析命,“玉大人主卫,你說我怎么就攤上這事【榉撸” “怎么了簇搅?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長软吐。 經(jīng)常有香客問我瘩将,道長,這世上最難降的妖魔是什么关噪? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮乌妙,結(jié)果婚禮上使兔,老公的妹妹穿的比我還像新娘。我一直安慰自己藤韵,他們只是感情好虐沥,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泽艘,像睡著了一般欲险。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匹涮,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天天试,我揣著相機與錄音,去河邊找鬼然低。 笑死喜每,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雳攘。 我是一名探鬼主播带兜,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吨灭!你這毒婦竟也來了刚照?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤喧兄,失蹤者是張志新(化名)和其女友劉穎无畔,沒想到半個月后啊楚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡檩互,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年特幔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闸昨。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚯斯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饵较,到底是詐尸還是另有隱情拍嵌,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布循诉,位于F島的核電站横辆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏茄猫。R本人自食惡果不足惜狈蚤,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望划纽。 院中可真熱鬧脆侮,春花似錦、人聲如沸勇劣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽比默。三九已至幻捏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間命咐,已是汗流浹背篡九。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留醋奠,地道東北人瓮下。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像钝域,于是被迫代替她去往敵國和親讽坏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理例证,服務(wù)發(fā)現(xiàn)路呜,斷路器,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 在第 08 篇的時候,我們已經(jīng)成功做出簡易的拖拉效果胀葱,今天要來做一個完整的應(yīng)用漠秋,而且是實務(wù)上有機會遇到但不好處理的...
    readilen閱讀 1,905評論 1 8
  • 羅素說“知識庆锦,愛,和同情心是我生活動力轧葛÷悖”能說出這句話來的,內(nèi)心必定是無比善良的尿扯。我一直都愿意相信這個世界好人居多...
    Amazingzy閱讀 613評論 0 1
  • 最近發(fā)現(xiàn)一個問題,大約是經(jīng)歷的事情多了辟宗,再悲慘的劇情都無法使我流眼淚爵赵,而漫不經(jīng)心的一點溫柔卻常常讓我濕了眼眶,久久...
    Cat_bye閱讀 327評論 0 0
  • NSRegularExpression用于將正則表達(dá)式用于匹配Unicode字符串泊脐,其實例不可更改空幻,且?guī)в谐跏蓟瘯r...
    縱橫而樂閱讀 14,983評論 5 22