如何在 React 中构建搜索栏
搜索栏是让网站上的内容更容易被发现的好方法。在本教程中,我们将使用 React 构建一个可访问的搜索栏组件。我们还将使用 React 测试库添加一些单元测试。
这是我们的最终产品:
本教程的源代码可在react-search-bar找到。
在应用中渲染搜索栏组件
首先,为你的搜索组件创建一个新文件。我将其命名为search.js
:
// src/search.js
const Search = () => {
return <div>Hello world!</div>
}
export default Search;
然后,从主应用程序文件内部渲染此组件:
// src/App.js
import Search from './search';
const App = () => {
return (
<Search />
);
}
export default App;
💡去哪儿了
import React from 'react'
?你可能会注意到,每个文件的顶部不再有 import 语句。从 React 17 开始,这不再是必需的。(耶!)如果你碰巧使用的是旧版本,你可能仍然需要这个 import 语句。
添加 HTML 元素
我们的搜索栏组件将包含几个 HTML 元素。添加一个标签、一个输入框和一个按钮,然后将它们全部包装在一个表单元素中:
// src/search.js
const SearchBar = () => (
<form action="/" method="get">
<label htmlFor="header-search">
<span className="visually-hidden">Search blog posts</span>
</label>
<input
type="text"
id="header-search"
placeholder="Search blog posts"
name="s"
/>
<button type="submit">Search</button>
</form>
);
export default SearchBar;
这将呈现如下效果:
可访问性和标签
您可能想知道为什么我们要将标签和占位符文本加倍。
这是因为占位符无法访问。通过添加标签,我们可以告诉屏幕阅读器用户输入字段的用途。
我们可以使用视觉隐藏的CSS 类来隐藏我们的标签:
// src/App.css
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
这使得屏幕阅读器用户可以看到它,但其他人看不到它。
现在,我们的搜索栏可以正常使用了!搜索时,您将导航到/?s=<your_query_here>
。
添加帖子列表
现在我们可以进行搜索了,我们需要一个搜索条目列表。我创建了一个虚假帖子列表:
const posts = [
{ id: '1', name: 'This first post is about React' },
{ id: '2', name: 'This next post is about Preact' },
{ id: '3', name: 'We have yet another React post!' },
{ id: '4', name: 'This is the fourth and final post' },
];
使用map
函数循环并渲染它们:
// src/App.js
const App = () => {
return (
<div>
<Search />
<ul>
{posts.map((post) => (
<li key={post.id}>{post.name}</li>
))}
</ul>
</div>
);
}
根据您的搜索查询过滤列表
当我们执行搜索时,搜索栏会将我们导航到一个新的 URL。我们可以从 URL 中获取以下值:
const { search } = window.location;
const query = new URLSearchParams(search).get('s');
我们还需要一个根据搜索查询过滤帖子的函数。如果你查询的列表很简单,你可以自己编写一个:
const filterPosts = (posts, query) => {
if (!query) {
return posts;
}
return posts.filter((post) => {
const postName = post.name.toLowerCase();
return postName.includes(query);
});
};
您还可以依靠第三方搜索库(如js-search)来为您过滤帖子。
使用搜索查询和过滤功能,您可以呈现与您的搜索相匹配的帖子:
// src/App.js
const App = () => {
const { search } = window.location;
const query = new URLSearchParams(search).get('s');
const filteredPosts = filterPosts(posts, query);
return (
<div>
<Search />
<ul>
{filteredPosts.map(post => (
<li key={post.key}>{post.name}</li>
))}
</ul>
</div>
);
}
现在,当您输入查询时,您将能够过滤您的帖子!
添加立即搜索或“输入时搜索”
除了按回车键提交搜索之外,您可能还希望列表在用户开始输入时进行过滤。从用户体验的角度来看,这种即时响应可能更令人愉悦。
要添加此功能,您可以searchQuery
在组件的状态中存储一个值,并在用户开始输入时更改该值:
// src/App.js
import { useState } from 'react';
function App() {
const { search } = window.location;
const query = new URLSearchParams(search).get('s');
const [searchQuery, setSearchQuery] = useState(query || '');
const filteredPosts = filterPosts(posts, searchQuery);
return (
<div>
<Search
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
/>
<ul>
{filteredPosts.map(post => (
<li key={post.key}>{post.name}</li>
))}
</ul>
</div>
);
}
传入searchQuery
和setSearchQuery
道具后,您需要在输入元素中使用它:
// src/search.js
const SearchBar = ({ searchQuery, setSearchQuery }) => (
<form action="/" method="get">
<label htmlFor="header-search">
<span className="visually-hidden">Search blog posts</span>
</label>
<input
value={searchQuery}
onInput={e => setSearchQuery(e.target.value)}
type="text"
id="header-search"
placeholder="Search blog posts"
name="s"
/>
<button type="submit">Search</button>
</form>
);
现在,只要您开始输入,您的帖子就会开始过滤!
使用 React Router 添加 SPA 导航
目前,按下 Enter 键时,搜索栏会刷新整个页面。
如果您要构建单页应用 (SPA),则需要使用像React Router这样的路由库。您可以使用以下命令安装它:
yarn add react-router-dom
安装后,将您的应用程序包装在Router
组件中:
// src/App.js
import { BrowserRouter as Router } from "react-router-dom";
const App = () => {
return <Router>
{ /* ... */ }
</Router>
}
然后将以下内容添加到搜索组件的顶部:
// src/search.js
import { useHistory } from 'react-router-dom';
const SearchBar = ({ searchQuery, setSearchQuery }) => {
const history = useHistory();
const onSubmit = e => {
history.push(`?s=${searchQuery}`)
e.preventDefault()
};
return <form action="/" method="get" autoComplete="off" onSubmit={onSubmit}>
现在,当用户按下回车键时,应用程序的 URL 将会改变,而无需刷新整个页面。
“边输入边搜索”、SPA 导航和可访问性问题
如果没有整页刷新,列表中的项目发生变化时,您将无法通知屏幕阅读器用户。我们可以使用ARIA 实时区域
发送这些通知。
谷歌了一下,发现有像react-aria-live和react-a11y-announcer这样的包可以帮你实现这个功能。
可惜的是,这两个包好像一年多都没更新了。
幸运的是,编写自己的播音员组件很简单:
// src/announcer.js
const Announcer = ({ message }) =>
<div role="region" aria-live="polite" className="visually-hidden">{message}</div>
export default Announcer;
然后在您的主应用程序组件中呈现它:
// src/App.js
<Announcer message={`List has ${filteredPosts.length} posts`}/>
每当组件中的消息发生变化时Announcer
,屏幕阅读器就会读出该消息。
现在,当您搜索时,屏幕阅读器用户将收到更新,让他们知道页面上有多少个帖子。
这不是一个完美的解决方案,但它比让你的物品默默改变要好得多。
如果您使用的是 Mac 并正在测试其 VoiceOver 功能,请务必使用 Safari!我发现其他浏览器与屏幕阅读器配合得不太好。
使用 React Testing Library 测试你的组件
最后,我们将使用 React 测试库来测试我们的组件。这个库是 create-react-app 中自带的。
我们要添加的第一个测试是使用 进行可访问性检查axe
。要使用它,请将jest-axe
包添加到您的存储库:
yarn add jest-axe
我们可以使用 axe 来测试我们的搜索组件是否存在任何可访问性违规:
// src/search.test.js
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import Search from '../src/search';
expect.extend(toHaveNoViolations);
test('should not have any accessibility violations', async () => {
const { container } = render(<Search searchQuery='' />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
这是一个非常容易捕获简单可访问性问题的方法。例如,如果我们删除了标签组件,测试就会失败:
我们还应该添加一个测试来测试组件的功能。让我们添加一个测试,当你输入“preact”时,它只显示一篇帖子:
// src/App.test.js
test('should render one post when user searches for preact', () => {
render(<App />);
let posts = screen.getAllByRole('listitem');
expect(posts.length).toEqual(4);
const searchBar = screen.getByRole('textbox');
userEvent.type(searchBar, 'preact');
posts = screen.getAllByRole('listitem');
expect(posts.length).toEqual(1);
});
结论
阅读完本教程后,您将能够为 React 应用创建一个可访问的搜索栏组件。并附带单元测试!您可以在react-search-bar查看完整的源代码。
如果您对如何让搜索栏更易于访问有更多了解,我很乐意听取您的意见。欢迎通过 Twitter 账号@emma_goto与我联系。
文章来源:https://dev.to/emma/how-to-build-a-search-bar-in-react-1lcn