使用Fuse.js將動態搜索添加到React應用

Fuse.js是一個輕量級的搜索引擎,能夠在用戶的瀏覽器中的客戶端運行。讓咱們看看如何使用它來輕鬆地爲React應用添加搜索功能。html

什麼時候使用Fuse.js

搜索功能對不少類型的網站都頗有用,可讓用戶高效地找到他們想要的東西。但爲何咱們會專門選擇使用Fuse.js呢?node

爲搜索提供動力的選擇有不少,最簡單的多是使用現有的數據庫。例如,Postgres有一個全文搜索功能。MySQL也有,Redis也有RediSearch模塊。react

還有一些專門的搜索引擎,其中Elasticsearch和Solr是最受歡迎的。這些搜索引擎須要更多的設置,但它們擁有你的用例可能須要的高級功能。git

最後,你可使用AlgoliaSwiftype等搜索即服務平臺。這些服務運行本身的搜索基礎架構。你只需經過API提供數據、配置和查詢。github

可是,您可能不須要這些解決方案所暴露的能力,這可能須要大量的工做來實現,更不用說成本了。若是沒有太多的數據須要搜索,Fuse.js須要最小的設置,而且能夠提供比您本身可能想到的更好的搜索體驗。shell

至於多少數據對Fuse.js來講是過多的,考慮到Fuse.js須要訪問整個數據集,因此你須要在客戶端所有加載。若是數據集的大小是100MB,那就超出了發送給客戶端的合理範圍。但若是它只有幾千字節,它多是Fuse.js的一個很好的候選者。數據庫

構建一個Fuse.js + React演示應用程序

讓咱們作一個基本的React應用,使用Fuse.js讓用戶搜索狗的品種。你能夠在這裏查看最終的結果,源代碼能夠在GitHub上找到。npm

咱們將從設置一些腳手架開始。從一個新的Node.js項目開始,咱們將安裝React和Fuse.js:json

npm install --save react react-dom fuse.js
//or
yarn add react react-dom fuse.js

咱們還將安裝Parcel做爲開發依賴項:swift

npm install --save-dev parcel@2.0.0-beta.1
//or
yarn add --dev parcel@2.0.0-beta.1

咱們將在 package.json 啓動腳本中使用它來編譯應用程序:

{  
  "scripts": {
    "start": "parcel serve ./index.html --open"
  }
}

接下來,咱們將建立一個barebones index.html,其中包含一個空的 div 供React渲染,以及一個 noscript 消息,以免在用戶禁用JavaScript時出現空白頁面。

<!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"));

此時,若是你運行 npm run startyarn run start,Parcel應該會在瀏覽器中打開網站,你應該會看到這個表單。

實施搜索

如今開始實施搜索,咱們將從顯示搜索結果的組件開始。咱們須要處理三種狀況:

  1. 用戶還沒有執行搜索時
  2. 沒有查詢結果時(由於咱們不但願用戶認爲某些問題)
  3. 何時有結果顯示

咱們將在ordered list中顯示全部結果。

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>
  );
}

咱們也來編寫本身的搜索函數。稍後,咱們將可以將咱們的簡單方法的結果與Fuse.js的結果進行比較。

咱們的方法很簡單:咱們將遍歷犬種數組(來自這個JSON列表),並返回包含整個搜索查詢的全部犬種。咱們還會讓全部東西都小寫,這樣搜索就不區分大小寫了。

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()));
}

接下來,讓咱們將全部內容連接在一塊兒,方法是從表單提交中獲取搜索查詢,而後執行搜索並顯示結果。

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很簡單,咱們須要導入它,讓它使用 new Fuse() 對數據進行索引,而後使用索引的搜索功能。搜索會返回一些元數據,因此咱們將只提取實際的項目進行展現。

import Fuse from "fuse.js";

const fuse = new Fuse(dogs);

function searchWithFuse(query) {
  if (!query) {
    return [];
  }

  return fuse.search(query).map((result) => result.item);
}

元數據包括一個 refIndex 整數,讓咱們能夠回溯到原始數據集中的相應項目。若是咱們用 new Fuse(dogs, {includeScore: true}) 初始化索引,咱們也會獲得匹配分數:一個介於0和1之間的值,其中0是徹底匹配。那麼「Husky」的搜索結果就會像這樣:

[
  {
    item: "Siberian Husky",
    refIndex: 386,
    score: 0.18224241177399383
  }
]

咱們將在 Search 組件的表單中添加一個複選框,讓用戶選擇是否使用Fuse.js而不是基本的搜索函數。

<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>

如今咱們能夠用Fuse.js進行搜索了!咱們可使用複選框來比較使用它和不使用它。

最大的區別在於Fuse.js能夠容忍錯別字(經過近似字符串匹配),而咱們的基本搜索則須要精確匹配。若是咱們把「retriever」拼錯爲「retreiver」,請查看基本搜索結果。

如下是針對同一查詢更有用的Fuse.js結果:

搜索多個字段

若是咱們關心多個字段,咱們的搜索可能會更復雜。例如,想象一下,咱們想經過品種和原產國來搜索。Fuse.js支持這種用例。當咱們建立索引時,咱們能夠指定要索引的對象鍵。

const dogs = [
  {breed: "Affenpinscher", origin: "Germany"},
  {breed: "Afghan Hound", origin: "Afghanistan"},
  // More breeds..
];

const fuse = new Fuse(dogs, {keys: ["breed", "origin"]});

如今,Fuse.js將同時搜索 breedorigin 字段。

總結

有時候,咱們不想花費資源去創建一個完整的Elasticsearch實例。當咱們有簡單的需求時,Fuse.js能夠提供相應的簡單解決方案。而正如咱們所看到的,將它與React一塊兒使用也是很簡單的。

即便咱們須要更高級的功能,Fuse.js也容許給不一樣的字段賦予不一樣的權重,添加 ANDOR 邏輯,調整模糊匹配邏輯等等。當你下次須要在應用中添加搜索功能時,能夠考慮使用它。