在現在的互聯網世界里,自動完成的搜索功能是一個很常見的功能。比如百度、搜狗、360搜索 ...
功能描述一下大概是這個樣子的:有一個搜索框,用戶在里面輸入要查詢的條件,系統會“智能”判斷用戶輸完了,然后自動根據條件去搜索相關的數據返回給用戶。
網上這個自動完成的插件很多,實現自動完成功能也不復雜,特別是像vue、angularjs、react這類可以實現雙向綁定的庫出現以后,實現就更方便了。本文不講自動完成功能的實現,而是介紹自動完成功能后續數據的請求該如何考慮,主要要處理下面兩個問題。
問題1:自動完成搜索觸發頻率如何控制?
問什么要控制自動完成搜索的觸發頻率呢?大家都知道,自動完成觸發基本上都是鍵盤響應事件,在文本框中輸入一個文本就觸發一次,這個太頻繁了。比如我要輸入的搜索條件是abcdefg,輸入a觸發一次,接着輸入b再觸發一次,接着輸入c又觸發一次...,等到我把這幾個字母全部輸完就觸發了7次,假設請求邏輯沒有限制的話,這就會發生7次數據請求,而實際上只有第7次的結果才是我想要的,其它6次數據請求完全是浪費資源。在用戶需求上來說,用戶真正希望的是搜索框能自動識別到用戶的abcdefg這一串字符全部輸入完了,然后再觸發請求數據。對於一些變態的輸入,比如按住某一個建不放,還不知道要觸發多少次。
所以控制自動完成搜索的觸發頻率還是很有必要的。當然,搜索框能完全智能的知道用戶所想然后去觸發,在目前來說還是做不到的。
我這里使用控制空閑時間的間隔的方式來限制自動完成搜索的觸發頻率。使用lodash庫中的debounce方法。
問題2:數據還在請求中時再次觸發請求,上次的請求如何取消?
為什么要取消上次的請求你?舉個例了,我輸入了查詢條件"xxx",數據請求發送了,我們暫且把它稱為請求1。因為某些原因(比如說網絡不好,這個條件的數據量大等等),請求1響應很慢,我們在請求1還沒用拿到數據的時候又輸入查詢條件"yyy"發送了請求2,沒想到這個請求2數據響應特別快,一下子就得到了數據data,我們准備把data展示出來,這時候請求1的數據回來了,data會被覆蓋掉,也就是說這時候我們用"yyy"的條件查詢得到了"xxx"條件的查詢結果。這個結果其實是超出用戶期望的。所以在發送新的請求之前,如果上次的請求還沒有結束,我們就需要取消掉它。
我這里使用axios的CancelToken來取消請求。
下面列出主要代碼:
hello.vue:
<template> <div class="hello"> <!--使用mint-ui的搜索組件--> <mt-search v-model="searchKey" @input="search"></mt-search> <span>{{result}}</span> </div> </template> <script> import _ from 'lodash'; //引入lodash import axios from 'axios' //引入axios //請求canceltoken列表 let sources = []; export default { name: 'hello', data () { return { searchKey: '', //查詢條件 result: '' //查詢結果 } }, methods: { //使用_.debounce控制搜索的觸發頻率 //准備搜索 search: _.debounce( function () { let that = this; //刪除已經結束的請求 _.remove(sources, function (n) { return n.source === null; }); //取消還未結束的請求 sources.forEach(function (item) { if (item !== null && item.source !== null && item.status === 1) { item.status = 0; item.source.cancel('取消上一個') } }); //創建新的請求cancelToken,並設置狀態請求中 var sc = { source: axios.CancelToken.source(), status: 1 //狀態1:請求中,0:取消中 }; //這個對象加入數組中 sources.push(sc);
//開始搜索數據,yourhttp替換成你自己的請求路徑 axios.get('yourhttp', { cancelToken: sc.source.token }).then(function (res) { //請求成功 sc.source = null; //置空請求canceltoken //TODO這里處理搜索結果 console.log(res.data); that.result = res.data; }).catch(function (thrown) { //請求失敗 sc.source = null; //置空請求canceltoken //下面的邏輯其實測試用 if (axios.isCancel(thrown)) { console.log('Request canceled', thrown.message); } else { //handle error } }); }, 500 //空閑時間間隔設置500ms ) } } </script> <style scoped> .mint-search { height: auto; } </style>
我的測試效果圖:

