使用 Autocomplete.js 创建多功能栏
什么时候搜索栏不再是搜索栏?当它是一个带有自动完成功能的“多功能栏”时!
在《与 Jason 一起学习》一集中,Sarah Dayan提到了使用自动完成功能来创造充满快捷方式和高级用户体验的想法。
在本教程中,我们将逐步讲解如何设置自动完成功能,并与 JavaScript 进行交互。具体来说,我们将构建一个多功能栏,用于切换网站的亮暗模式。多功能栏是一个搜索栏,包含搜索内容和可执行的操作。Chrome 或 Firefox 的搜索和 URL 栏就是一个很好的例子。
用户可以在搜索字段中输入/
命令。这些命令将与特定的 JavaScript 方法绑定触发。我们还将使自动完成结果具有状态。当应用处于亮色模式时,亮色模式选项将显示“已启用”标志。当暗色模式启用时,暗色模式选项将显示该标志。
亲自尝试一下吧!
配置自动完成功能以用于 React
Autocomplete 本质上是一个原生的 JavaScript 库。让我们将其挂载为 React 组件,使其可复用性更强,方便在任何基于 React 的框架或网站中使用。
我们将从 CodeSandbox 的基础 React 沙盒开始。fork此沙盒以获取已安装所有软件包的准确起始点。
要创建组件,我们首先要添加一个名为 的新文件Autocomplete.js
。此文件将包含自动完成库的所有初始化代码,并导出组件以供我们应用程序使用。
在新文件的顶部,从 React、React-dom 和 Autocomplete 库导入必要的元素。
import React, { createElement, Fragment, useEffect, useRef } from "react";
import { render } from "react-dom";
import { autocomplete } from "@algolia/autocomplete-js";
导入后,我们需要导出一个新的 React 功能组件。我们将从创建一个新的已挂载组件的基本样板开始。
export function Autocomplete(props) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) {
return undefined;
}
// Space to initialize autocomplete on the newly created container
// Destroy the search instance in cleanup
return () => {
search.destroy();
};
}, [props]);
return <div ref={containerRef} />;
}
此代码将负责安装和卸载时组件的基本初始化和故障。
在函数内部,是时候初始化 Autocomplete 实例了。
// Creates an Autcomplete component from the JS library
// https://www.algolia.com/doc/ui-libraries/autocomplete/guides/using-react/
export function Autocomplete(props) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) {
return undefined;
}
// Initialize autocomplete on the newly created container
const search = autocomplete({
container: containerRef.current,
renderer: { createElement, Fragment },
// Autocomplete render()
// https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-render
render({ children }, root) {
// react-dom render
// https://reactjs.org/docs/react-dom.html#render
render(children, root);
},
...props
});
// Destroy the search instance in cleanup
return () => {
search.destroy();
};
}, [props]);
return <div ref={containerRef} />;
}
该autocomplete
方法接受一个选项对象。我们将container
属性设置为此函数创建的元素。通过指定该renderer
函数,我们可以使用 React 的createElement
方法和Fragment
组件。
然后,我们需要为 Autocomplete 提供一个render
函数。该函数将接受一个要渲染的组件对象 ( children
) 以及一个要附加实例的元素 ( root
)。
然后,我们可以使用任何方法来渲染这些项目。在本例中,我们将使用react-dom
的render()
方法并向其传递相同的元素。最后,我们希望autocomplete
在使用组件时将添加到组件的任何额外 props 传递给该方法。这将允许进行即时自定义。
使用<Autocomplete />
组件
移动到App.js
文件,我们可以导入我们的自动完成组件(以及一些默认样式)。
// Styles
import "./styles.css";
import "@algolia/autocomplete-theme-classic";
// Import algolia and autocomplete needs
import { Autocomplete } from "./Autocomplete";
现在,我们就可以把自动完成字段添加到页面上了。在App()
函数的 JSX 返回值中,我们可以把<Autocomplete />
组件放在 UI 合适的任何位置。我建议把它放在页面正文的后面。
export default function App() {
return (
<div className="App">
<h1 className="text-xl">
Run JS from{" "}
<a href="https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/">
Autocomplete
</a>
</h1>
<p className="text-base">
This demo is based on the amazing idea of{" "}
<a href="https://twitter.com/frontstuff_io">Sarah Dayan</a> in her
appearance on{" "}
<a href="https://www.learnwithjason.dev/javascript-autocomplete">
Learn with Jason
</a>
.
</p>
<p>
Use the Autocomplete box below to toggle dark mode and perform other
JS-driven actions on the page.
</p>
<Autocomplete />
{/* ... the rest of the function ... */}
</div>
)
}
自动完成组件可以接受autocomplete-js
库中允许的任何属性作为选项。首先,让我们添加占位符文本。
<Autocomplete placeholder="Try /dark" />
我们的应用中应该会出现一个搜索字段,其中设置了占位符文本。此字段目前还没有任何功能。让我们添加一些数据来完成它。
actions
向自动完成组件添加源
自动完成库能够针对多个数据源创建自动完成功能。在本例中,我们只有一个静态数据源,但任何外部数据(包括 Algolia 索引)都可以用来填充此功能。
要添加源,我们将使用getSources
prop 并提供一个接受query
选项的函数。此查询是用户主动在输入框中输入的内容。我们可以使用它来检查数据中的项目。
源是 getSources 返回数组中的一个对象。源所需的基本元素包括:一个sourceId
字符串、一个template
用于渲染的对象和一个getItems()
返回数据的函数。目前,我们只返回一个带有 label 属性的静态数组。这足以填充我们的自动完成功能。我们还可以添加openOnFocus
一个 prop,以便在用户聚焦该字段时自动列出我们的项目。
<Autocomplete
placeholder="Try /dark"
openOnFocus
getSources={({ query }) => [
{
sourceId: "actions",
templates: {
item({ item }) {
return <h3>{item.label}</h3>
}
},
getItems({ state }) {
return [
{
label: "/dark"
},
{
label: "/light"
}
]
}
}
]}
/>
现在,我们的字段里已经有项目了,但我们并没有在输入时进行过滤。让我们用几个辅助函数来解决这个问题。
过滤并突出显示自动完成项目
使用 Algolia 索引时,我们可以使用一些辅助函数来管理过滤和高亮显示,但我们并没有使用 Algolia 索引。在我们的用例中,我们希望将其完全保留在浏览器中。为此,我们需要一些辅助函数来正确地过滤和高亮显示我们的选项。
使用 JavaScript RegExp() 过滤自动完成项目
JavaScript 提供了基于正则表达式测试来过滤数组的功能。为此,我们需要创建一个模式来测试用户输入的任何组合。让我们基于该查询创建一个辅助函数,并在 JS.filter()
方法中使用它。
在App.js
导出之外,我们将创建新的辅助函数getQueryPattern()
。
function getQueryPattern(query, flags = "i") {
const pattern = new RegExp(
`(${query
.trim() // Trim leading and ending whitespace
.toLowerCase() // convert to lower case
.split(" ") // Split on spaces for multiple commands
.map((token) => `^${token}`) // Map over the resulting array and create Regex_
.join("|")})`, // Join those expressions with an OR |
flags
);
return pattern;
}
export default function App() { /* ... */ }
getItems()
一旦创建了辅助函数,我们将在返回项目数组之前在方法中创建模式。
保存模式后,我们可以用它来测试我们的数组。
<Autocomplete
placeholder="Try /dark"
openOnFocus
getSources={({ query }) => [
{
sourceId: "actions",
templates: {
item({ item }) {
return <h3>{item.label}</h3>
}
},
getItems({ state }) {
const pattern = getQueryPattern(query);
return [
{
label: "/dark"
},
{
label: "/light"
}
].filter(({ label }) => pattern.test(label)) // tests the label against the pattern
}
}
]}
/>
现在,当我们/dark
在输入框中输入时,只有/dark
选项。我们还没有给用户任何提示,告诉他们为什么这样做。让我们添加一个小的突出显示功能,以显示输入的字母。
突出显示结果中输入的字符串
为了突出显示键入的文本,我们需要采用查询文本和我们在上一步中创建的模式,并生成一个新字符串,在键入的文本周围添加额外的标记。
在辅助函数之后getQueryPattern
,让我们创建一个新的highlight
辅助函数。
function highlight(text, pattern) {
// Split the text based on the pattern
const tokens = text.split(pattern);
// Map over the split text and test against the pattern
return tokens.map((token) => {
// If the pattern matches the text, wrap the text in <mark>
if (!pattern.test("") && pattern.test(token)) {
return <mark>{token}</mark>;
}
// return the token back to the array
return token;
});
}
此辅助函数采用要测试的文本和要检查的模式,并返回带有附加标记的字符串。
我们首先根据模式拆分文本。这将得到一个包含两部分的数组——匹配项和不匹配项。当我们映射这个新数组时,我们可以根据模式检查文本,如果匹配,则将该特定项包装在新的标记中。如果不匹配,则返回未修改的文本。
<Autocomplete
placeholder="Try /dark"
openOnFocus
getSources={({ query }) => [
{
sourceId: "actions",
templates: {
item({ item }) {
return <h3>{item.highlighted}</h3>
}
},
getItems({ state }) {
const pattern = getQueryPattern(query);
return [
{
label: "/dark"
},
{
label: "/light"
}
]
.filter(({ label }) => pattern.test(label)) // tests the label against the pattern
.map((action) => ({
...action,
highlighted: highlight(action.label, pattern)
}));
}
}
]
}
/>
有了该辅助函数,我们现在可以映射所有已筛选的项目。我们将获取操作项,并返回一个对象,该对象包含其所有初始属性,但新增了一个包含我们高亮文本的属性。该属性由操作项的属性和我们之前定义的模式 highlighted
构建而成。label
现在action.label
,我们将模板中的 改为使用新highlight
属性。/dark
在字段中输入 时,该项将正确高亮显示文本。
过滤 UI 已经完成,但是当我们选择一个项目时,没有任何反应。让我们解决这个问题。
在自动完成功能中触发 JavaScript 函数onSelect
数组中的每个源都getSources
可以有自己的onSelect
方法。此方法定义了用户通过键盘或点击选择选项时的功能。
让我们首先创建一个全局选择函数来记录项目的数据,然后将查询重置为空白字符串。
getSources = {({ query }) => [
{
sourceId: "actions",
templates: {
item({ item }) {
return <h3>{item.highlighted}</h3>
}
},
// Run this code when item is selected
onSelect(params) {
// item is the full item data
// setQuery is a hook to set the query state
const { item, setQuery } = params;
console.log(item)
setQuery("");
},
}
对于一个动作,我们可以在这个方法中定义 JavaScript,但是为了使其可以在将来的任何动作中重复使用,让我们在项目的数据上定义该方法。
为此,我们将为onSelect
每个项目定义一个方法。此方法可以处理您需要的任何功能。在本例中,我们将创建一个非常简单的暗模式和亮模式,方法是将类添加dark
到主体以启用暗模式,将其移除以启用亮模式。
{
label: "/light",
onSelect() {
document.querySelector("body").classList.remove("dark");
notify("Light Mode enabled");
}
},
{
label: "/dark",
onSelect() {
document.querySelector("body").classList.add("dark");
notify("Dark Mode enabled");
}
},
现在,回到 mainonSelect
方法,不再运行console.log(item)
,而是运行item.onSelect()
。这将触发我们刚刚创建的函数。
我们现在有有效的行动!
增强多功能栏体验
通过采取实际行动,我们可以集中精力为多功能栏打造强大的用户体验。
自动突出显示并选择
首先,让我们让自动完成功能自动突出显示列表中的第一个项目。这样用户只需按 Enter 键即可选择操作。
要添加此功能,我们需要向组件传递一个新的 prop <Autocomplete />
。通过向该 prop 传递defaultActiveItemId
一个值"0"
,我们可以使列表中的第一个项目处于活动状态。任何活动项目都可以通过按 Enter 键来选择。这带来了稳定的键盘体验。
使用新组件创建更强大的 UI
我们将抽象化为template
使用一个名为 的单独组件Action
。我们可以在单独的文件中构建它,也可以在 中创建它App.js
。
要使用该组件,我们需要传递一个hit
包含项目数据的 prop。该组件还将使用与本教程开头导入的经典主题中的特定项目匹配的特定类名。
在标记内部,我们提供了高亮显示的文本和两个新项目:hit.icon
以及回车键的 SVG 表示。这为我们的操作添加了一些自定义图标,并指示用户可以使用回车键来选择项目。
function Action({ hit }) {
// Component to display the items
return (
<div className="aa-ItemWrapper">
<div className="aa-ItemContent">
<div className="aa-ItemIcon">{hit.icon}</div>
<div className="aa-ItemContentBody">
<div className="aa-ItemContentTitle">
<span>{hit.highlighted}</span>
</div>
</div>
</div>
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly"
type="button"
title="Select"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
</svg>
</button>
</div>
</div>
);
}
一旦创建了组件,我们就需要更改item
模板来使用它。
templates: {
item({ item }) {
return <Action hit={item} />;
}
}
我们还需要为每个操作项添加一个图标属性。在这个例子中,我们有一些手工制作的 SVG,但任何图标库都可以使用。
return [
{
icon: (
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
),
label: "/dark",
enabled: state.context.dark,
onSelect({ setContext }) {
document.querySelector("body").classList.add("dark");
}
},
{
icon: (
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
),
label: "/light",
onSelect() {
document.querySelector("body").classList.remove("dark");
notify("Light Mode enabled");
}
},
]
看起来真不错。网站处于浅色模式有点奇怪,但浅色模式选项没有任何提示。我们来为用户添加一些上下文信息。
使用以下方式创建启用状态setContext
自动完成功能让我们可以访问状态。让我们使用它来创建一个enabled
状态,并在触发操作时设置该状态。
让我们首先为每个动作添加一个名为 的新属性enabled
。
{ //...
label: "/dark",
enabled: state.context.dark,
// ...
},
{ //...
label: "/light",
enabled: !state.context.dark,
// ...
}
此属性将检查自动完成功能的状态对象中是否存在标记为 的上下文项dark
。如果dark
设置为true
,则暗色操作将具有 trueenabled
状态;如果false
,则亮色操作将具有 true 状态。
为了获得该上下文,我们需要在函数运行期间设置应用程序的上下文onSelect
。我们可以将该setContext
方法传递给onSelect
函数,并使用它将其设置dark
为 true 或 false。
setContext
我们需要在选项对象中为 sources 方法传递方法。首先将其更改getSources={({ query })}
为getSources={({ query, setContext })}
。然后我们就可以在函数setContext
中使用了onSelect
。
onSelect({ setContext }) {
document.querySelector("body").classList.remove("dark");
setContext({ dark: false });
}
现在剩下的就是enabled
在我们的组件中使用布尔值。
function Action({ hit }) {
// Component to display the items
return (
<div className="aa-ItemWrapper">
<div className="aa-ItemContent">
<div className="aa-ItemIcon">{hit.icon}</div>
<div className="aa-ItemContentBody">
<div className="aa-ItemContentTitle">
<span>{hit.highlighted}</span>
{hit.enabled && (
<code className="aa-ItemContentTitleNote">Enabled</code>
)}
</div>
</div>
</div>
<div className="aa-ItemActions">
<button
className="aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly"
type="button"
title="Select"
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
</svg>
</button>
</div>
</div>
);
}
这样,我们的多功能栏就具备状态了。这是一个相对简单的暗黑模式示例。为了进一步扩展,您可以根据应用的整体状态或用户本地存储中的信息来添加和设置多功能栏的上下文。
后续步骤Next steps
在本教程中,我们构建了自动完成功能,使其不仅仅是搜索,您还可以使用不同的源对象及其自带的模板集来添加常规搜索功能。您还可以扩展操作以匹配应用程序可能存在的任何操作。
一些想法:
- 添加到待办事项列表或已保存列表
- 订阅新闻简报
- 用户个人资料更新
我们很期待看到你的成果。请 fork初始沙盒(或者这个已完成的沙盒),创作一些新内容,然后在 Twitter或下方评论区与我们分享。
鏂囩珷鏉ユ簮锛�https://dev.to/algolia/creating-an-omnibar-with-autocomplete-3jgh