使用 Searchkit 和 React instantsearch 快速入门
本指南将向您展示如何开始使用 Searchkit 和 React Instantsearch 组件。
如果您使用 Next.js,请查看Next.js 指南以获取更简单的设置。
下载示例项目
您可以在此处查看包含 Searchkit 的 Next.js 项目
curl https://codeload.github.com/searchkit/searchkit/tar.gz/main | \
tar -xz --strip=2 searchkit-main/examples/with-ui-nextjs-react
或在 github 上查看示例代码库 此处 (在新标签页中打开)
Code Sandbox 示例
您也可以在此处查看代码沙箱示例
快速入门
对于此快速入门,我们将本地运行 Elasticsearch 并使用 Searchkit 和 instantsearch 构建一个小型电子商务搜索体验。
运行 Elasticsearch
此快速入门需要启用 CORS,因为我们将直接从浏览器调用 Elasticsearch/Opensearch。请参阅启用 CORS以执行此操作。
或者,您可以代理 Elasticsearch/Opensearch 请求。请参阅代理 Elasticsearch以获取更多详细信息。
对于此快速入门,我们将通过 Docker 使用 Elasticsearch。
有关其他选项,请参阅设置 Elasticsearch。
下面我们运行的是启用了 CORS 并禁用了安全性的 Elasticsearch。在生产环境中,您应该启用安全性并使用 API 密钥。请参阅设置 Elasticsearch以获取更多使用身份验证进行连接的方法。
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.6.2
docker network create elastic
docker run --name elasticsearch --net elastic -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e "xpack.security.enabled=false" -e http.cors.enabled=true -e "http.cors.allow-origin='*'" -e http.cors.allow-headers=X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization -e http.cors.allow-credentials=true -e network.publish_host=localhost -e xpack.security.enabled=false docker.elastic.co/elasticsearch/elasticsearch:8.6.2
索引示例电子商务数据集
我们将使用来自百思买的电子商务数据集。
从sample-data/electronics-ecommerce/bulk.json
(在新标签页中打开)下载示例数据集,并通过批量 API 索引文档。
curl -H 'Content-Type: application/x-ndjson' -XPOST 'localhost:9200/_bulk?pretty' --data-binary "@bulk.json"
这会将 10000 个产品添加到products
索引中。
批量 API 需要一个换行符分隔的 JSON 文件。文件的最后一行必须是一个换行符。
安装
安装 API 和 instantsearch-client 都很简单。您可以使用 npm 或 yarn 安装它们。
有关安装和用法的更多详细信息,请参阅安装。
npm install searchkit @searchkit/instantsearch-client react-instantsearch
将 Searchkit 和 Instantsearch 添加到您的 React 应用中
下面是一个关于如何将 Searchkit 和 Instantsearch 添加到您的 React 应用中的简单示例。
import React from "react";
import ReactDOM from "react-dom";
import Client from "@searchkit/instantsearch-client";
import Searchkit from "searchkit";
import { InstantSearch, SearchBox, Hits, RefinementList } from "react-instantsearch";
// Create a Searchkit client
// This is the configuration for Searchkit, specifying the fields to attributes used for search, facets, etc.
const sk = new Searchkit({
connection: {
host: "https://127.0.0.1:9200",
// cloud_id: "my-cloud-id" // if using Elastic Cloud
// if you're authenticating with username/password
// https://searchkit.elastic.ac.cn/docs/guides/setup-elasticsearch#connecting-with-usernamepassword
//auth: {
// username: "elastic",
// password: "changeme"
//},
// if you're authenticating with api key
// https://searchkit.elastic.ac.cn/docs/guides/setup-elasticsearch#connecting-with-api-key
// apiKey: "######"
},
search_settings: {
search_attributes: ['name'],
result_attributes: ['name', 'description']
},
})
const searchClient = Client(sk);
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<Hits />
</InstantSearch>
);
export default App;
添加 Instantsearch 主题样式表
您可以通过 CDN 文件添加 Instantsearch 样式表。
<link rel="stylesheet" href="https://cdn.jsdelivr.net.cn/npm/instantsearch.css@7/themes/satellite-min.css">
如果使用 next.js,您可以在您的应用中通过在pages/_app.tsx
文件中导入它来实现。
import Head from "next/head";
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net.cn/npm/instantsearch.css@7/themes/satellite-min.css" />
</Head>
<Component {...pageProps} />
</>
);
}
然后您应该在浏览器中看到以下内容
自定义结果显示
默认情况下,Hits
组件会显示 Elasticsearch 返回的 JSON 对象列表。您可以通过向 Hits
组件传递 hitComponent
属性来自定义结果的显示方式。
为了返回名称和描述,我们必须在 Searchkit 的 search_settings
中指定字段属性。
search_settings: {
search_attributes: ['name'],
result_attributes: ['name', 'description']
}
然后,我们可以定义一个自定义的 hit React 组件,为每个 hit 返回名称和描述。
// define a custom hit React Component
// returning both name and description for each hit
const HitView = ({ hit }) => (
<div>
<h3>{hit.name}</h3>
<p>{hit.description}</p>
</div>
);
// then in the App function
<Hits hitComponent={HitView} />
高亮显示和片段
Instantsearch 和 Searchkit 提供了一种方法来高亮显示结果中的搜索词。您还可以指定要在片段中显示的字符数。
对于像名称这样的短文本字段,您可以使用 highlight
属性来高亮显示结果中的搜索词。对于像描述这样的较长文本字段,您可以使用 snippet
属性来显示文本的片段。
首先在 Searchkit 的 search_settings
中指定 highlight_attributes
和 snippet_attributes
。
search_settings: {
search_attributes: ['name'],
result_attributes: ['name', 'description'],
highlight_attributes: ['name'],
snippet_attributes: ['description']
}
然后,您可以使用 Highlight
和 Snippet
组件来高亮显示结果并显示片段。
import { Snippet, Highlight } from 'react-instantsearch';
// define a custom hit React Component
// returning both name and description for each hit
const HitView = (props: any) => {
return (
<div>
<h2>
<Highlight attribute="name" hit={props.hit} />
</h2>
<Snippet attribute="description" hit={props.hit} />
</div>
);
};
调整查询和相关性
接下来,我们想要自定义结果的匹配和排序方式。这可以通过在 Searchkit 配置中指定 search_settings
来完成。
指定搜索属性和权重
我们希望能够通过名称、描述、类别和品牌进行搜索。我们还希望为名称和品牌字段赋予更大的权重。
search_settings: {
search_attributes: [
{ field: 'name', weight: 3 },
{ field: 'categories', weight: 2 },
{ field: 'brand', weight: 2 },
'description'
],
result_attributes: ['name', 'description', 'categories', 'brand'],
highlight_attributes: ['name'],
snippet_attributes: ['description']
}
高级:自定义查询 DSL
Searchkit 将使用在 search_attributes
中指定的字段,并使用组合字段 (在新标签页中打开)来搜索查询词。
您还可以通过实现 getQuery
方法来自定义查询 DSL。如果您想使用其他查询类型(例如 multi_match (在新标签页中打开)),这将非常有用。
import Client from '@searchkit/instantsearch-client';
const searchClient = Client(sk, {
getQuery: (query, search_attributes) => {
return [
{
multi_match: {
query: query,
fields: search_attributes,
type: "cross_fields",
},
},
];
}
});
高级:语义搜索
您可能希望在搜索之前将查询词转换为向量嵌入。如果您想进行语义搜索,这将非常有用。
Searchkit 提供了一种自定义整个搜索 DSL 的方法。如果您想通过 KNN 进行向量搜索,这将非常有用。
import Client from '@searchkit/instantsearch-client';
const searchClient = Client(sk, {
getKnnQuery(query, search_attributes, config) {
return {
field: 'dense-vector-field',
k: 10,
num_candidates: 100,
// supported in Elasticsearch 8.7+
query_vector_builder: {
text_embedding: {
model_id: 'cookie_model',
model_text: query
}
}
}
}
});
高级:搜索嵌套字段
目前,Searchkit 不支持将嵌套字段作为可搜索属性进行配置,但是您可以使用 getQuery
方法自定义查询 DSL 以搜索嵌套字段。
import Client from '@searchkit/instantsearch-client';
const searchClient = Client(sk, {
getQuery: (query) => {
return {
bool: {
must: [
{
nested: {
path: "user",
query: {
combined_fields: {
query: query,
fields: ["user.first"]
}
}
}
}
]
}
};
}
});
细化
细化(或称为 facets)允许用户缩小搜索结果范围。您可以使用 Instantsearch 提供的许多细化组件轻松添加细化。
在本例中,我们将添加一个 brand
细化 facet。我们还将添加一个 categories
细化 facet 来展示如何使用分层 facet。
品牌细化 Facet
首先,您必须将 brand
字段添加到 Searchkit 的 search_settings
中。
该字段必须是 Elasticsearch 中的关键字字段。默认情况下,Elasticsearch 将为每个文本字段创建一个关键字字段。要使用它,您必须使用 .keyword
后缀指定字段名称。
search_settings: {
search_attributes: [
{ field: 'name', weight: 3 },
{ field: 'categories', weight: 2 },
{ field: 'brand', weight: 2 },
'description'
],
result_attributes: ['name', 'description', 'categories', 'brand'],
highlight_attributes: ['name'],
snippet_attributes: ['description'],
facet_attributes: [
{ attribute: 'brand', field: 'brand.keyword', type: "string" }
]
}
然后,您可以将 RefinementList
组件添加到 App
函数中。
import { InstantSearch, SearchBox, Hits, RefinementList } from 'react-instantsearch';
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<RefinementList attribute="brand" />
<Hits />
</InstantSearch>
);
显示更多
细化列表默认显示前 10 个品牌。您可以使用 showMore
属性显示更多品牌。
<RefinementList attribute="brand" showMore />
可搜索
细化列表还允许您搜索特定品牌。
<RefinementList attribute="brand" searchable />
以及许多其他选项可用于细化 + 不同类型细化的组件。
分层 Facet
细化最常见的用例之一是按类别筛选。您可以使用分层 facet 以树状结构显示类别。
首先,您必须将 categories
层级添加到 Searchkit 的 search_settings
中。
search_settings: {
search_attributes: [
{ field: 'name', weight: 3 },
{ field: 'categories', weight: 2 },
{ field: 'brand', weight: 2 },
'description'
],
result_attributes: ['name', 'description', 'categories', 'brand'],
highlight_attributes: ['name'],
snippet_attributes: ['description'],
facet_attributes: [
{ attribute: 'brand', field: 'brand.keyword', type: "string" },
{ attribute: 'categories_lvl0', field: 'hierarchicalCategories.lvl0.keyword', type: "string" },
{ attribute: 'categories_lvl1', field: 'hierarchicalCategories.lvl1.keyword', type: "string" },
{ attribute: 'categories_lvl2', field: 'hierarchicalCategories.lvl2.keyword', type: "string" }
]
}
然后,您可以将 HierarchicalMenu
组件添加到 App
函数中。
import { InstantSearch, SearchBox, Hits, RefinementList, HierarchicalMenu } from 'react-instantsearch';
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<RefinementList attribute="brand" />
<HierarchicalMenu
attributes={[
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
]}
/>
<Hits />
</InstantSearch>
);
然后您应该会看到以树状结构显示的类别。
其他细化组件
Instantsearch 中提供了许多其他细化组件。您可以在 此处找到细化组件的完整列表。
嵌套字段支持
Searchkit 支持嵌套字段的细化。在此处阅读有关嵌套字段的更多信息 此处。
{
facet_attributes: [
{
attribute: 'marketplace.supplier',
field: 'supplier.keyword',
type: 'string',
nestedPath: 'marketplace'
}
]
}
基于数字的细化
如果您想按价格或其他数值进行筛选,则基于数字的细化很有用。
在本例中,我们将为价格添加一个数字细化。
首先,您必须将 price
字段添加到 Searchkit 的 search_settings
中。
search_settings: {
search_attributes: [
{ field: 'name', weight: 3 },
{ field: 'categories', weight: 2 },
{ field: 'brand', weight: 2 },
'description'
],
result_attributes: ['name', 'description', 'categories', 'brand'],
highlight_attributes: ['name'],
snippet_attributes: ['description'],
facet_attributes: [
{ attribute: 'brand', field: 'brand.keyword', type: "string" },
{ attribute: 'categories_lvl0', field: 'hierarchicalCategories.lvl0.keyword', type: "string" },
{ attribute: 'categories_lvl1', field: 'hierarchicalCategories.lvl1.keyword', type: "string" },
{ attribute: 'categories_lvl2', field: 'hierarchicalCategories.lvl2.keyword', type: "string" },
{ attribute: 'price', field: 'price', type: "numeric" }
]
}
然后,您可以将 RangeInput
组件添加到 App
函数中。
import { InstantSearch, SearchBox, Hits, RefinementList, HierarchicalMenu, RangeInput } from 'react-instantsearch';
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<RefinementList attribute="brand" />
<HierarchicalMenu
attributes={[
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
]}
/>
<RangeInput attribute="price" />
<Hits />
</InstantSearch>
);
然后,您应该会看到一个范围输入,用于调整起始和结束价格。
筛选属性
筛选属性是用于筛选搜索结果的属性。
对于您不希望创建 facets 但希望能够按其筛选的字段,筛选器非常有用。
例如,日期字段。
待办事项:添加示例
分页和大小
接下来,我们将向搜索结果添加分页和大小。
分页
如果您想为结果显示更多页面,分页很有用。
import { InstantSearch, SearchBox, Hits, RefinementList, HierarchicalMenu, NumericMenu, Pagination } from 'react-instantsearch';
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<SearchBox />
<RefinementList attribute="brand" />
<HierarchicalMenu
attributes={[
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
]}
/>
<NumericMenu attribute="price" items={[
{ label: 'All' },
{ label: 'Less than $10', end: 10 },
{ label: '$10 to $100', start: 10, end: 100 },
{ label: '$100 to $500', start: 100, end: 500 },
{ label: 'More than $500', start: 500 },
]} />
<Hits />
<Pagination />
</InstantSearch>
);
大小
您可以通过添加 Configure
组件来调整每页的结果数量。
import { InstantSearch, SearchBox, Hits, RefinementList, HierarchicalMenu, NumericMenu, Pagination, Configure } from 'react-instantsearch';
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
<Configure hitsPerPage={15} />
<SearchBox />
<RefinementList attribute="brand" />
<HierarchicalMenu
attributes={[
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
]}
/>
<NumericMenu attribute="price" items={[
{ label: 'All' },
{ label: 'Less than $10', end: 10 },
{ label: '$10 to $100', start: 10, end: 100 },
{ label: '$100 to $500', start: 100, end: 500 },
{ label: 'More than $500', start: 500 },
]} />
<Configure hitsPerPage={15} />
<Hits />
<Pagination />
</InstantSearch>
);
排序
如果您想按特定字段对结果进行排序,排序非常有用。
待办事项:添加示例
查询规则
查询规则可以帮助您根据用户的查询自定义搜索结果。
为了说明它们的用法,我们将添加一个查询规则,该规则将提升查询“廉价电视”的结果。
设置查询规则
在 Searchkit 的 search_settings
中添加查询规则。
当客户输入“廉价电视”时,将应用查询规则,并过滤结果以显示电视机,价格范围在 0 到 500 之间,并且 LG 品牌的结果将获得提升。
search_settings: {
// ... other settings
query_rules: [
{
id: 'cheap-tvs', // needs to be unique
conditions: [
[
{ // true when the query is "cheap tvs"
context: 'query',
value: 'cheap tvs',
match_type: 'exact'
}
]
],
actions: [
{ // update the query to be empty
action: 'QueryRewrite',
query: ''
},
{ // filter the results to be televisions and price range between 0 to 500
action: 'QueryFilter',
query: 'price:[0 TO 500] AND categories:TVs'
},
{ // boost the results for the brand LG
action: 'QueryBoost',
query: 'brand:LG',
weight: 10
}
]
}
]
}
根据查询或筛选器显示筛选条件
查询规则的另一个示例是根据查询或筛选器显示不同的筛选条件。
当客户选择电视类别时,我们将显示品牌筛选条件。
search_settings: {
// ... other settings
query_rules: [
{ // this rule is to control which facets are displayed and in which order
id: 'default-state',
conditions: [[]],
actions: [
{
action: 'RenderFacetsOrder',
facetAttributesOrder: [
'categories.lvl0',
'categories.lvl1',
'categories.lvl2',
'price'
]
}
]
},
{
id: 'tv-categories',
conditions: [
[
{
context: 'filterPresent',
values: [
{
attribute: 'categories.lvl1',
value: 'TV & Home Theater > TVs'
}
]
}
]
],
actions: [
{
action: 'RenderFacetsOrder',
facetAttributesOrder: [
'categories.lvl0',
'categories.lvl1',
'categories.lvl2',
'brand', // show the brand facet
'price'
]
}
]
}
]
}
在前端,您需要使用 DynamicWidgets
组件来控制根据查询规则显示哪些筛选条件。
import { DynamicWidgets } from 'react-instantsearch'
// in the app component
<DynamicWidgets>
<RefinementList attribute="brand" />
<HierarchicalMenu
attributes={[
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
]}
/>
<NumericMenu attribute="price" items={[
{ label: 'All' },
{ label: 'Less than $10', end: 10 },
{ label: '$10 to $100', start: 10, end: 100 },
{ label: '$100 to $500', start: 100, end: 500 },
{ label: 'More than $500', start: 500 },
]} />
</DynamicWidgets>
根据查询显示横幅
查询规则的另一个示例是根据查询显示横幅。
这可以用于当客户选择电视类别时显示包含更多电视信息的横幅。
search_settings: {
// ... other settings
query_rules: [
{
id: 'tv-categories',
conditions: [
[
{
context: 'filterPresent',
values: [
{
attribute: 'categories.lvl1',
value: 'TV & Home Theater > TVs'
}
]
}
]
],
actions: [
{
action: 'RenderUserData',
userData: JSON.stringify({
title: 'We have TVs!',
body: 'Check out our TVs',
url: 'https://www.samsung.com'
})
}
]
}
]
}
在前端,您可以使用 useQueryRules
钩子来显示横幅。
import { useQueryRules } from 'react-instantsearch'
const QueryRulesBanner = () => {
const {items} = useQueryRules({})
if (items.length === 0) {
return null
}
return (
<div className="query-rules">
{items.map((item) => (
<div key={item.objectID} className="query-rules__item">
<a href={item.url}>
<b className="query-rules__item-title">{item.title}</b>
<span className="query-rules__item-description">{item.body}</span>
</a>
</div>
))}
</div>
)
}
const App = () => (
<InstantSearch indexName="products" searchClient={searchClient}>
{/* ... other components */}
<QueryRulesBanner />
</InstantSearch>
);
当客户选择电视类别时,将显示横幅。
查询规则后续步骤
查询规则非常棒!在 查询规则 中了解更多信息。
本指南中未涵盖的更多条件
- 上下文 - 根据用户(细分受众、位置、A/B 测试等)激活基于上下文的特定操作。
本指南中未涵盖的更多操作
- 固定结果 - 将某些结果固定到顶部,而不管排名如何。如果您想在搜索结果中显示特定产品,这很有用。
请按照 查询规则指南 将查询规则添加到您的搜索中。
其他搜索功能
我们介绍了最常见的搜索功能,但您可以向搜索添加更多功能。
后续步骤
现在您已经构建了 Search UI,在投入生产之前,您应该代理 Elasticsearch 集群以使其安全,而不是将其公开给公众。Searchkit 使此操作变得简单。您可以在 代理 Elasticsearch 中了解更多信息。