如何在 React 中构建搜索栏

2025-05-24

如何在 React 中构建搜索栏

搜索栏是让网站上的内容更容易被发现的好方法。在本教程中,我们将使用 React 构建一个可访问的搜索栏组件。我们还将使用 React 测试库添加一些单元测试。

这是我们的最终产品:

本教程的源代码可在react-search-bar找到。

在应用中渲染搜索栏组件

首先,为你的搜索组件创建一个新文件。我将其命名为search.js

// src/search.js
const Search = () => {
    return <div>Hello world!</div>
}

export default Search;
Enter fullscreen mode Exit fullscreen mode

然后,从主应用程序文件内部渲染此组件:

// src/App.js
import Search from './search';

const App = () => {
    return (
        <Search />
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

💡去哪儿了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;
Enter fullscreen mode Exit fullscreen mode

这将呈现如下效果:

可访问性和标签

您可能想知道为什么我们要将标签和占位符文本加倍。

这是因为占位符无法访问。通过添加标签,我们可以告诉屏幕阅读器用户输入字段的用途。

我们可以使用视觉隐藏的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;
}
Enter fullscreen mode Exit fullscreen mode

这使得屏幕阅读器用户可以看到它,但其他人看不到它。

现在,我们的搜索栏可以正常使用了!搜索时,您将导航到/?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' },
];
Enter fullscreen mode Exit fullscreen mode

使用map函数循环并渲染它们:

// src/App.js
const App = () => {
    return (
        <div>
            <Search />
            <ul>
                {posts.map((post) => (
                    <li key={post.id}>{post.name}</li>
                ))}
            </ul>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

根据您的搜索查询过滤列表

当我们执行搜索时,搜索栏会将我们导航到一个新的 URL。我们可以从 URL 中获取以下值:

const { search } = window.location;
const query = new URLSearchParams(search).get('s');
Enter fullscreen mode Exit fullscreen mode

我们还需要一个根据搜索查询过滤帖子的函数。如果你查询的列表很简单,你可以自己编写一个:

const filterPosts = (posts, query) => {
    if (!query) {
        return posts;
    }

    return posts.filter((post) => {
        const postName = post.name.toLowerCase();
        return postName.includes(query);
    });
};
Enter fullscreen mode Exit fullscreen mode

您还可以依靠第三方搜索库(如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>
    );
}
Enter fullscreen mode Exit fullscreen mode

现在,当您输入查询时,您将能够过滤您的帖子!

添加立即搜索或“输入时搜索”

除了按回车键提交搜索之外,您可能还希望列表在用户开始输入时进行过滤。从用户体验的角度来看,这种即时响应可能更令人愉悦。

要添加此功能,您可以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>
    );
}
Enter fullscreen mode Exit fullscreen mode

传入searchQuerysetSearchQuery道具后,您需要在输入元素中使用它:

// 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>
);
Enter fullscreen mode Exit fullscreen mode

现在,只要您开始输入,您的帖子就会开始过滤!

使用 React Router 添加 SPA 导航

目前,按下 Enter 键时,搜索栏会刷新整个页面。
如果您要构建单页应用 (SPA),则需要使用像React Router这样的路由库。您可以使用以下命令安装它:

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

安装后,将您的应用程序包装在Router组件中:

// src/App.js
import { BrowserRouter as Router } from "react-router-dom";

const App = () => {
    return <Router>
        { /* ... */ }
    </Router>
}
Enter fullscreen mode Exit fullscreen mode

然后将以下内容添加到搜索组件的顶部:

// 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}>
Enter fullscreen mode Exit fullscreen mode

现在,当用户按下回车键时,应用程序的 URL 将会改变,而无需刷新整个页面。

“边输入边搜索”、SPA 导航和可访问性问题

如果没有整页刷新,列表中的项目发生变化时,您将无法通知屏幕阅读器用户。我们可以使用ARIA 实时区域
发送这些通知

谷歌了一下,发现有像react-aria-livereact-a11y-announcer这样的包可以帮你实现这个功能。
可惜的是,这两个包好像一年多都没更新了。

幸运的是,编写自己的播音员组件很简单:

// src/announcer.js
const Announcer = ({ message }) =>
    <div role="region" aria-live="polite" className="visually-hidden">{message}</div>

export default Announcer;
Enter fullscreen mode Exit fullscreen mode

然后在您的主应用程序组件中呈现它:

// src/App.js
<Announcer message={`List has ${filteredPosts.length} posts`}/>
Enter fullscreen mode Exit fullscreen mode

每当组件中的消息发生变化时Announcer,屏幕阅读器就会读出该消息。

现在,当您搜索时,屏幕阅读器用户将收到更新,让他们知道页面上有多少个帖子。

这不是一个完美的解决方案,但它比让你的物品默默改变要好得多。

如果您使用的是 Mac 并正在测试其 VoiceOver 功能,请务必使用 Safari!我发现其他浏览器与屏幕阅读器配合得不太好。

使用 React Testing Library 测试你的组件

最后,我们将使用 React 测试库来测试我们的组件。这个库是 create-react-app 中自带的。

我们要添加的第一个测试是使用 进行可访问性检查axe。要使用它,请将jest-axe包添加到您的存储库:

yarn add jest-axe
Enter fullscreen mode Exit fullscreen mode

我们可以使用 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();
});

Enter fullscreen mode Exit fullscreen mode

这是一个非常容易捕获简单可访问性问题的方法。例如,如果我们删除了标签组件,测试就会失败:

我们还应该添加一个测试来测试组件的功能。让我们添加一个测试,当你输入“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);
});

Enter fullscreen mode Exit fullscreen mode

结论

阅读完本教程后,您将能够为 React 应用创建一个可访问的搜索栏组件。并附带单元测试!您可以在react-search-bar查看完整的源代码。

如果您对如何让搜索栏更易于访问有更多了解,我很乐意听取您的意见。欢迎通过 Twitter 账号@emma_goto与我联系。

文章来源:https://dev.to/emma/how-to-build-a-search-bar-in-react-1lcn
PREV
10 个你不知道需要的 HTML 元素:音频、引用、输出、图片、进度表、模板、时间、视频、分词机会
NEXT
算法注释