使用Fuse.js將動態(tài)搜索添加到React應(yīng)用
Fuse.js是一個輕量級的搜索引擎,可以在用戶的瀏覽器中的客戶端運(yùn)行。讓我們看看如何使用它來輕松地為React應(yīng)用添加搜索功能。
何時使用Fuse.js
搜索功能對很多類型的網(wǎng)站都很有用,可以讓用戶高效地找到他們想要的東西。但為什么我們會專門選擇使用Fuse.js呢?
為搜索提供動力的選擇有很多,最簡單的可能是使用現(xiàn)有的數(shù)據(jù)庫。例如,Postgres有一個全文搜索功能。MySQL也有,Redis也有RediSearch模塊。
還有一些專門的搜索引擎,其中Elasticsearch和Solr是最受歡迎的。這些搜索引擎需要更多的設(shè)置,但它們擁有你的用例可能需要的高級功能。
最后,你可以使用Algolia或Swiftype等搜索即服務(wù)平臺。這些服務(wù)運(yùn)行自己的搜索基礎(chǔ)架構(gòu)。你只需通過API提供數(shù)據(jù)、配置和查詢。
但是,您可能不需要這些解決方案所暴露的能力,這可能需要大量的工作來實現(xiàn),更不用說成本了。如果沒有太多的數(shù)據(jù)需要搜索,F(xiàn)use.js需要最小的設(shè)置,并且可以提供比您自己可能想到的更好的搜索體驗。
至于多少數(shù)據(jù)對Fuse.js來說是過多的,考慮到Fuse.js需要訪問整個數(shù)據(jù)集,所以你需要在客戶端全部加載。如果數(shù)據(jù)集的大小是100MB,那就超出了發(fā)送給客戶端的合理范圍。但如果它只有幾千字節(jié),它可能是Fuse.js的一個很好的候選者。
構(gòu)建一個Fuse.js + React演示應(yīng)用程序
讓我們做一個基本的React應(yīng)用,使用Fuse.js讓用戶搜索狗的品種。你可以在這里查看最終的結(jié)果,源代碼可以在GitHub上找到。
我們將從設(shè)置一些腳手架開始。從一個新的Node.js項目開始,我們將安裝React和Fuse.js:
- npm install --save react react-dom fuse.js
- //or
- yarn add react react-dom fuse.js
我們還將安裝Parcel作為開發(fā)依賴項:
- npm install --save-dev parcel@2.0.0-beta.1
- //or
- yarn add --dev parcel@2.0.0-beta.1
我們將在 package.json 啟動腳本中使用它來編譯應(yīng)用程序:
- {
- "scripts": {
- "start": "parcel serve ./index.html --open"
- }
- }
接下來,我們將創(chuàng)建一個barebones index.html,其中包含一個空的 div 供React渲染,以及一個 noscript 消息,以避免在用戶禁用JavaScript時出現(xiàn)空白頁面。
- <!DOCTYPE html>
- <html lang="en">
- <body>
- <div id="app"></div>
- <noscript>
- <p>Please enable JavaScript to view this page.</p>
- </noscript>
- <script src="./index.js"></script>
- </body>
- </html>
我們將使我們的 index.js 簡單的開始。我們將渲染一個有搜索查詢輸入的表單,盡管我們還不會實際處理搜索。
- import React, { useState } from "react";
- import ReactDom from "react-dom";
- function Search() {
- return (
- <form>
- <label htmlFor="query">Search for a dog breed:</label>
- <input type="search" id="query" />
- <button>Search</button>
- </form>
- );
- }
- ReactDom.render(<Search />, document.getElementById("app"));
此時,如果你運(yùn)行 npm run start 或 yarn run start,Parcel應(yīng)該會在瀏覽器中打開網(wǎng)站,你應(yīng)該會看到這個表單。
實施搜索
現(xiàn)在開始實施搜索,我們將從顯示搜索結(jié)果的組件開始。我們需要處理三種情況:
- 用戶尚未執(zhí)行搜索時
- 沒有查詢結(jié)果時(因為我們不希望用戶認(rèn)為某些問題)
- 什么時候有結(jié)果顯示
我們將在ordered list中顯示所有結(jié)果。
- function SearchResults(props) {
- if (!props.results) {
- return null;
- }
- if (!props.results.length) {
- return <p>There are no results for your query.</p>;
- }
- return (
- <ol>
- {props.results.map((result) => (
- <li key={result}>{result}</li>
- ))}
- </ol>
- );
- }
我們也來編寫自己的搜索函數(shù)。稍后,我們將能夠?qū)⑽覀兊暮唵畏椒ǖ慕Y(jié)果與Fuse.js的結(jié)果進(jìn)行比較。
我們的方法很簡單:我們將遍歷犬種數(shù)組(來自這個JSON列表),并返回包含整個搜索查詢的所有犬種。我們還會讓所有東西都小寫,這樣搜索就不區(qū)分大小寫了。
- const dogs = [
- "Affenpinscher",
- "Afghan Hound",
- "Aidi",
- "Airedale Terrier",
- "Akbash Dog",
- "Akita",
- // More breeds..
- ];
- function searchWithBasicApproach(query) {
- if (!query) {
- return [];
- }
- return dogs.filter((dog) => dog.toLowerCase().includes(query.toLowerCase()));
- }
接下來,讓我們將所有內(nèi)容鏈接在一起,方法是從表單提交中獲取搜索查詢,然后執(zhí)行搜索并顯示結(jié)果。
- function Search() {
- const [searchResults, setSearchResults] = useState(null);
- return (
- <>
- <form
- onSubmit={(event) => {
- event.preventDefault();
- const query = event.target.elements.query.value;
- const results = searchWithBasicApproach(query);
- setSearchResults(results);
- }}
- >
- <label htmlFor="query">Search for a dog breed:</label>
- <input type="search" id="query" />
- <button>Search</button>
- </form>
- <SearchResults results={searchResults} />
- </>
- );
- }
添加Fuse.js
使用Fuse.js很簡單,我們需要導(dǎo)入它,讓它使用 new Fuse() 對數(shù)據(jù)進(jìn)行索引,然后使用索引的搜索功能。搜索會返回一些元數(shù)據(jù),所以我們將只提取實際的項目進(jìn)行展示。
- import Fuse from "fuse.js";
- const fuse = new Fuse(dogs);
- function searchWithFuse(query) {
- if (!query) {
- return [];
- }
- return fuse.search(query).map((result) => result.item);
- }
元數(shù)據(jù)包括一個 refIndex 整數(shù),讓我們可以回溯到原始數(shù)據(jù)集中的相應(yīng)項目。如果我們用 new Fuse(dogs, {includeScore: true}) 初始化索引,我們也會得到匹配分?jǐn)?shù):一個介于0和1之間的值,其中0是完全匹配。那么“Husky”的搜索結(jié)果就會像這樣:
- [
- {
- item: "Siberian Husky",
- refIndex: 386,
- score: 0.18224241177399383
- }
- ]
我們將在 Search 組件的表單中添加一個復(fù)選框,讓用戶選擇是否使用Fuse.js而不是基本的搜索函數(shù)。
- <form
- onSubmit={(event) => {
- event.preventDefault();
- const query = event.target.elements.query.value;
- const useFuse = event.target.elements.fuse.checked;
- setSearchResults(
- useFuse ? searchWithFuse(query) : searchWithBasicApproach(query)
- );
- }}
- >
- <label htmlFor="query">Search for a dog breed: </label>
- <input type="search" id="query" />
- <input type="checkbox" name="fuse" />
- <label htmlFor="fuse"> Use Fuse.js</label>
- <button>Search</button>
- </form>
現(xiàn)在我們可以用Fuse.js進(jìn)行搜索了!我們可以使用復(fù)選框來比較使用它和不使用它。
最大的區(qū)別在于Fuse.js可以容忍錯別字(通過近似字符串匹配),而我們的基本搜索則需要精確匹配。如果我們把“retriever”拼錯為“retreiver”,請查看基本搜索結(jié)果。
以下是針對同一查詢更有用的Fuse.js結(jié)果:
搜索多個字段
如果我們關(guān)心多個字段,我們的搜索可能會更復(fù)雜。例如,想象一下,我們想通過品種和原產(chǎn)國來搜索。Fuse.js支持這種用例。當(dāng)我們創(chuàng)建索引時,我們可以指定要索引的對象鍵。
- const dogs = [
- {breed: "Affenpinscher", origin: "Germany"},
- {breed: "Afghan Hound", origin: "Afghanistan"},
- // More breeds..
- ];
- const fuse = new Fuse(dogs, {keys: ["breed", "origin"]});
現(xiàn)在,F(xiàn)use.js將同時搜索 breed 和 origin 字段。
總結(jié)
有時候,我們不想花費(fèi)資源去建立一個完整的Elasticsearch實例。當(dāng)我們有簡單的需求時,F(xiàn)use.js可以提供相應(yīng)的簡單解決方案。而正如我們所看到的,將它與React一起使用也是很簡單的。
即使我們需要更高級的功能,F(xiàn)use.js也允許給不同的字段賦予不同的權(quán)重,添加 AND 和 OR 邏輯,調(diào)整模糊匹配邏輯等等。當(dāng)你下次需要在應(yīng)用中添加搜索功能時,可以考慮使用它。