MDN右上角的搜索效果:展示搜索结果高亮。
实现该效果主要分两个步骤:搜索 ➕ 高亮。
- 搜索结果展示使用的是downshift 🏎
- 搜索功能使用的是flexSearch 🔍
🔍 flexSearch 搜索
flexSearch的工作原理:根据初始化(new)选项将待查询的数据集注册(add)生成如下图的数组存储的是注册时的id集合,根据搜索时(search)选项从数据集合中筛选符合条件的id集合。
引入flexSearch
👇以下代码以vue项目为例
npm install flexsearch@0.7.0
const { Index, Document, Worker } = require("flexsearch");
options 其他选项
new Index({
tokenize: 'full',
resolution: 5,
encode: (str: string) => str.trim().toLowerCase().split(/[\p{Z}\p{S}\p{P}\p{C}]+/u)
})
tokenize
- 可选值
full
reverse
forward
strict
(default) strict
在add
阶段可以注册 encode 编码后的整个单词,在 search 阶段也会讲搜索内容进行编码- 其他值可以匹配encode编码后的字符子集,
reverse
表示从后往前forward
表示从前往后。且有 full 包含 reverse 包含 forward ;例如 htmlfull
会被分成 "html", "htm", "ht", "h", "tml", "tm", "t", "ml", "m", "l"reverse
会被分成 "l", "ml", "tml", "h", "ht", "htm", "html"forward
会被分成 "h", "ht", "htm", "html"strict
会被分成 website
resolution
resolution 存储索引的容器大小。定义
flexSearch.map
的长度,默认为9。
context
context is just supported by tokenizer "strict"
- 需要配合 tokenize: 'strict' 使用
- context.depth: 2(左图) 和 3(右图) 可以定义
flexSearch.map
的深度(树形结构) 如下图👇 "html 开发技术 test 学习 开发"
- bidirectional 决定方向
- context.bidirectional: false 会被匹配成
{html: {"开发技术": [0]}}
- context.bidirectional: true 会被匹配成
{"开发技术": {html: [0]}}
charset
charset可以选择FlexSearch的内部编码策略。格式为 latin: *** 可选值有default(默认), simple, balance, advanced, extra; flexSearch 根据编码策略定义选项
encode
,rtl
,tokenize
,但是初始化时的encode
,rtl
,tokenize
优先级高于 charset
例如 charset: 'latin:advanced'
例如 advanced 通过encode方法会将 web -> fep
encode
编码类型。选择一个内置函数或传递一个自定义编码函数。
- 可以根据 charset 的配置选用内置函数。
- 也可以自己定义
encode: (str: string) => str.trim().toLowerCase().split(''),
返回值为数组;会对add时的title以及search时的query进行编码。
optimize
它为索引使用一个内存优化的堆栈流。默认为true。
optimize: true
map: {0: {}, 1: {}};optimize: false
map: [];
minlength
定义最少存放的字符长度,默认为1
boost
在将内容索引到索引时使用的自定义增强函数。仅支持在 strict 模式下使用。
function (words[], term, index) => Float
三个参数:所有单词的数组,当前项和当前项的索引。返回值**<1表示相关性降低,>1表示相关性增加**,可以用在降低某些搜索项的关联性。
lang
自定义语言或者内置语言,lang: {matcher, stemmer, filter}
rtl
支持从右到左的编码。
Index
初始化 new
const indexIndex = new Index({
tokenize: 'full'
});
const dataList = [{title, url}];
添加注册索引 add
dataList.forEach(({ title }, index) => {
indexIndex.add(index, title);
});
搜索 search
const indexResults = indexIndex.search({
query: q,
limit,
suggest: true
});
const result = indexResults.map(
(index: number) => (dataList || [])[index]
);
- query 表示搜索的内容,搜索过程中会根据 option.encode 进行编码;
- limit 限制最多搜索数量,默认100;
- suggest: true 可以实现 搜索
web abc
可以返回包含web
的数据; search 得到的结果indexResults
为dataList
的索引index集合。
完整片段
const dataList = [
{"title":"html 开发技术 web 学习技术 开发","url":"html/zh-CN/docs/Web"},
{"title":"html CSS JavaScript","url":"/zh-CN/docs/Web/JavaScript"},
{"title":"JavaScript 参考","url":"/zh-CN/docs/Web/JavaScript/Reference"},
{"title":"Web API 接口参考","url":"/zh-CN/docs/Web/API"},
]
if(!indexIndex){
indexIndex = new FlexSearch.Index({
tokenize: 'full',
});
dataList!.forEach(({ title }, i) => {
indexIndex.add(i, title);
});
}
let indexResults = indexIndex.search({
query: q,
limit: 50,
});
return indexResults.map(
(index: number) => (dataList || [])[index]
);
Document
options
1. index/field: Array
创建多个Index模式的对象,可用于多个字段的搜索。例如
field: ['title', 'url']
2. tag: string
tag 给标签起一个名称,可以在注册或查找时给数据进行分类。add/search 中表示分类的key为该字符串。 例如:
tag: 'tagName'
;则 add/search时候需要配置属性tagName: 'content'
3. worker: boolean
是否开启 Worker 模式
add
添加注册时需要根据初始化时的index/field
字段来注册,且可以设置tag
documentIndex.add({id: i, title, url, tag: tag})
search
1. tag
add 的时候配置了多个tag,可以选择那些tag中进行搜索。
tag: [ 'content', 'link' ]
2. bool 结合 tag 进行逻辑运算。and
表示从各tag中取交集,且按照tag的先后顺序取交集。
完整代码
const dataList = [
{"title":"html 开发技术 web 学习技术 开发","url":"html/zh-CN/docs/Web", tag: ['content', 'link']},
{"title":"html CSS JavaScript","url":"/zh-CN/docs/Web/JavaScript", tag: ['link']},
{"title":"JavaScript 参考","url":"/zh-CN/docs/Web/JavaScript/Reference", tag: 'link'},
{"title":"Web API 接口参考","url":"/zh-CN/docs/Web/API", tag: 'link'},
]
if(!documentIndex){
documentIndex = new FlexSearch.Document({
tokenize: 'full',
field: [ 'title', 'url' ],
tag: 'tag',
});
dataList!.forEach(({title, url, tag}, i) => {
documentIndex.add({id: i, title, url, tag: tag})
});
}
const words = q.trim().toLowerCase().split(/[ ,]+/);
let indexResults: searchItem[] = [];
const documentResults = documentIndex.search(q, {
limit,
bool: 'and',
tag: [ 'content', 'link' ]
})
Worker
Worker 模式是通过 async 异步方式实现搜索功能。
触发方式
1. Index 模式
new Worker(option)
2. Document 模式
option 中设置worker: true
工作原理
- 创建worker
// worker.js
import Worker from './index.worker.js'
this.worker = new worker()
- 通信
// worker.js
// 主线程 发送消息
this.worker.postMessage({
"task": "init",
"factory": factory,
"options": options
});
// 主线程 接收消息
this.worker["on"]("message", function(msg){
_self.resolver[msg["id"]](msg["msg"]) ;
});
// index.worker.js
// worker线程接受消息onmessage后发送消息postMessage
self.onmessage = (data) => {
postMessage({ "id": id, "msg": message });
}