Fuse.js:打造极致模糊搜索体验

Fuse.js 完全学习指南:JavaScript模糊搜索库

什么是 Fuse.js?

Fuse.js 是一个轻量、强大且无依赖的JavaScript模糊搜索库。它提供了简单而强大的模糊搜索功能,可以在任何 JavaScript 环境中使用,包括浏览器和 Node.js。

核心特点

  • 轻量级:压缩后仅 ~12KB,无外部依赖
  • 模糊搜索:支持拼写错误、部分匹配等容错搜索
  • 高度可配置:提供丰富的配置选项控制搜索行为
  • 多字段搜索:支持在对象的多个字段中搜索
  • 权重系统:不同字段可以设置不同的搜索权重
  • 高亮显示:支持搜索结果高亮显示
  • 跨平台:支持浏览器、Node.js、Deno等环境
  • TypeScript支持:提供完整的TypeScript类型定义

安装与引入

NPM 安装

# 使用 npm
npm install fuse.js

# 使用 yarn
yarn add fuse.js

引入方式

ES6 模块语法
import Fuse from 'fuse.js'
CommonJS
const Fuse = require('fuse.js')
直接

案例3:React Hooks 集成

import React, { useState, useMemo, useCallback } from 'react'
import Fuse from 'fuse.js'

// 自定义 Hook:useFuseSearch
function useFuseSearch(data, options) {
  const [query, setQuery] = useState('')
  
  const fuse = useMemo(() => {
    return new Fuse(data, options)
  }, [data, options])
  
  const results = useMemo(() => {
    if (!query.trim()) {
      return data.map((item, index) => ({ item, refIndex: index }))
    }
    return fuse.search(query)
  }, [fuse, query, data])
  
  return {
    query,
    setQuery,
    results,
    search: useCallback((searchQuery) => {
      return fuse.search(searchQuery)
    }, [fuse])
  }
}

// 主搜索组件
function ProductSearch() {
  const products = [
    {
      id: 1,
      name: 'MacBook Pro 16寸',
      brand: 'Apple',
      category: '笔记本电脑',
      price: 16999,
      tags: ['高性能', '创作', '专业']
    },
    {
      id: 2,
      name: 'iPhone 14 Pro',
      brand: 'Apple', 
      category: '智能手机',
      price: 7999,
      tags: ['摄影', '5G', '高端']
    },
    {
      id: 3,
      name: 'Surface Laptop 5',
      brand: 'Microsoft',
      category: '笔记本电脑',
      price: 8888,
      tags: ['轻薄', '办公', '便携']
    },
    {
      id: 4,
      name: 'Galaxy S23 Ultra',
      brand: 'Samsung',
      category: '智能手机',
      price: 9999,
      tags: ['大屏', 'S Pen', '摄影']
    }
  ]

  const searchOptions = {
    keys: [
      { name: 'name', weight: 0.4 },
      { name: 'brand', weight: 0.3 },
      { name: 'category', weight: 0.2 },
      { name: 'tags', weight: 0.1 }
    ],
    threshold: 0.3,
    includeMatches: true,
    includeScore: true
  }

  const { query, setQuery, results } = useFuseSearch(products, searchOptions)
  const [sortBy, setSortBy] = useState('relevance')

  // 排序结果
  const sortedResults = useMemo(() => {
    const sorted = [...results]
    
    switch (sortBy) {
      case 'price-asc':
        return sorted.sort((a, b) => a.item.price - b.item.price)
      case 'price-desc':
        return sorted.sort((a, b) => b.item.price - a.item.price)
      case 'name':
        return sorted.sort((a, b) => a.item.name.localeCompare(b.item.name))
      default:
        return sorted // 保持相关性排序
    }
  }, [results, sortBy])

  // 高亮匹配文本
  const highlightMatches = (text, matches) => {
    if (!matches || matches.length === 0) return text
    
    const match = matches[0]
    if (!match) return text
    
    let highlighted = text
    const indices = match.indices
    
    for (let i = indices.length - 1; i >= 0; i--) {
      const [start, end] = indices[i]
      highlighted = highlighted.slice(0, start) + 
                  '' + 
                  highlighted.slice(start, end + 1) + 
                  '' + 
                  highlighted.slice(end + 1)
    }
    
    return highlighted
  }

  return (
    

️ 产品搜索

{/* 搜索栏 */}
setQuery(e.target.value)} placeholder="搜索产品名称、品牌或分类..." style={{ width: '100%', padding: '12px', fontSize: '16px', border: '2px solid #ddd', borderRadius: '8px', outline: 'none' }} />
{/* 排序选项 */}
排序: 找到 {sortedResults.length} 个结果
{/* 搜索结果 */}
{sortedResults.map((result) => { const product = result.item const matches = result.matches || [] const nameMatch = matches.find(m => m.key === 'name') const brandMatch = matches.find(m => m.key === 'brand') return (

品牌: {' | '} 分类:{product.category}

{product.tags.map(tag => ( {tag} ))}

¥{product.price.toLocaleString()}
{result.score && (
匹配度: {Math.round((1 - result.score) * 100)}%
)}
) })}
{sortedResults.length === 0 && query && (
没有找到匹配的产品
)}
) } export default ProductSearch

性能优化建议

1. 大数据集处理

// 对于大数据集,考虑使用 Web Workers
class FuseWorker {
  constructor(data, options) {
    this.worker = new Worker('/fuse-worker.js')
    this.worker.postMessage({ type: 'init', data, options })
  }
  
  search(query) {
    return new Promise((resolve) => {
      this.worker.onmessage = (e) => {
        if (e.data.type === 'search-result') {
          resolve(e.data.results)
        }
      }
      this.worker.postMessage({ type: 'search', query })
    })
  }
}

// fuse-worker.js
let fuse

self.onmessage = function(e) {
  const { type, data, options, query } = e.data
  
  if (type === 'init') {
    importScripts('https://cdn.jsdelivr.net/npm/[email protected]/dist/fuse.min.js')
    fuse = new Fuse(data, options)
  }
  
  if (type === 'search' && fuse) {
    const results = fuse.search(query)
    self.postMessage({ type: 'search-result', results })
  }
}

2. 防抖搜索

// 使用防抖避免频繁搜索
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value)
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)
    
    return () => {
      clearTimeout(handler)
    }
  }, [value, delay])
  
  return debouncedValue
}

// 在组件中使用
function SearchComponent() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 300)
  
  const results = useMemo(() => {
    if (!debouncedQuery) return []
    return fuse.search(debouncedQuery)
  }, [debouncedQuery])
  
  // ...
}

3. 结果缓存

// 简单的 LRU 缓存实现
class LRUCache {
  constructor(capacity) {
    this.capacity = capacity
    this.cache = new Map()
  }
  
  get(key) {
    if (this.cache.has(key)) {
      const value = this.cache.get(key)
      this.cache.delete(key)
      this.cache.set(key, value)
      return value
    }
    return null
  }
  
  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    } else if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
}

// 带缓存的搜索函数
const searchCache = new LRUCache(100)

function cachedSearch(fuse, query) {
  const cached = searchCache.get(query)
  if (cached) return cached
  
  const results = fuse.search(query)
  searchCache.set(query, results)
  return results
}

️ 高级功能

1. 自定义评分函数

const options = {
  // 自定义字段权重
  getFn: (obj, path) => {
    // 自定义字段获取逻辑
    if (path === 'fullName') {
      return `${obj.firstName} ${obj.lastName}`
    }
    return obj[path]
  },
  
  // 自定义排序函数
  sortFn: (a, b) => {
    // 优先显示完全匹配
    if (a.score === 0 && b.score !== 0) return -1
    if (a.score !== 0 && b.score === 0) return 1
    
    // 按分数排序
    return a.score - b.score
  }
}

2. 动态更新索引

class DynamicFuse {
  constructor(initialData, options) {
    this.options = options
    this.data = [...initialData]
    this.fuse = new Fuse(this.data, options)
  }
  
  add(item) {
    this.data.push(item)
    this.rebuildIndex()
  }
  
  remove(predicate) {
    this.data = this.data.filter(item => !predicate(item))
    this.rebuildIndex()
  }
  
  update(predicate, updater) {
    this.data = this.data.map(item => 
      predicate(item) ? updater(item) : item
    )
    this.rebuildIndex()
  }
  
  rebuildIndex() {
    this.fuse = new Fuse(this.data, this.options)
  }
  
  search(query) {
    return this.fuse.search(query)
  }
}

3. 多语言支持

// 多语言搜索配置
const multiLanguageOptions = {
  keys: [
    'title.zh',
    'title.en', 
    'description.zh',
    'description.en'
  ],
  threshold: 0.3,
  
  // 自定义获取函数支持多语言
  getFn: (obj, path) => {
    const locale = getCurrentLocale() // 获取当前语言
    
    if (path.includes('.')) {
      const [field, lang] = path.split('.')
      return obj[field] && obj[field][lang]
    }
    
    return obj[path]
  }
}

// 多语言数据示例
const multiLanguageData = [
  {
    id: 1,
    title: {
      zh: '苹果手机',
      en: 'Apple iPhone'
    },
    description: {
      zh: '高端智能手机',
      en: 'Premium smartphone'
    }
  }
]

最佳实践

1. 合理设置阈值

// 不同场景的阈值建议
const thresholds = {
  exact: 0.0,        // 精确匹配
  strict: 0.2,       // 严格搜索
  moderate: 0.4,     // 中等容错
  loose: 0.6,        // 宽松搜索
  veryLoose: 0.8     // 非常宽松
}

// 根据数据类型选择合适的阈值
const getThreshold = (dataType) => {
  switch (dataType) {
    case 'email':
    case 'id':
      return thresholds.exact
    case 'name':
    case 'title':
      return thresholds.moderate
    case 'description':
    case 'content':
      return thresholds.loose
    default:
      return thresholds.moderate
  }
}

2. 优化搜索键配置

// 智能权重分配
const getSearchKeys = (dataFields) => {
  return dataFields.map(field => {
    let weight = 0.1 // 默认权重
    
    // 根据字段类型分配权重
    if (field.includes('title') || field.includes('name')) {
      weight = 0.4
    } else if (field.includes('tag') || field.includes('category')) {
      weight = 0.3
    } else if (field.includes('description') || field.includes('content')) {
      weight = 0.2
    }
    
    return { name: field, weight }
  })
}

3. 错误处理

class SafeFuse {
  constructor(data, options) {
    try {
      this.fuse = new Fuse(data, options)
      this.isReady = true
    } catch (error) {
      console.error('Fuse.js 初始化失败:', error)
      this.isReady = false
    }
  }
  
  search(query) {
    if (!this.isReady) {
      console.warn('Fuse.js 未就绪,返回原始数据')
      return []
    }
    
    try {
      return this.fuse.search(query)
    } catch (error) {
      console.error('搜索出错:', error)
      return []
    }
  }
}

总结

Fuse.js 是一个功能强大且易用的模糊搜索库,适用于各种 JavaScript 应用场景:

简单易用:API设计简洁,上手快速
功能丰富:支持模糊搜索、权重配置、高亮显示等
高度可配置:提供30+个配置选项满足不同需求
性能优秀:轻量级设计,适合大数据集处理
跨平台支持:浏览器、Node.js、Deno 全平台兼容
TypeScript友好:完整的类型定义支持

通过合理配置和优化,Fuse.js 可以为您的应用提供专业级的搜索体验,大大提升用户满意度。


开始您的智能搜索之旅吧!

开发建议:在实际项目中,建议结合防抖、缓存、虚拟滚动等技术,构建高性能的搜索系统。

你可能感兴趣的:(前端,插件,javascript,开发语言,ecmascript)