发布于 2026-01-06 0 阅读
0

构建一个无障碍的 React 组件:第 1 部分 - Breadcrumbs DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

构建易于访问的 React 组件:第一部分 - 面包屑导航

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

这篇文章最初发布在我的个人博客网站上。


上周二,我们开启了“构建无障碍 React 组件”系列教程第一期,幸运的是,我们转了个轮盘,它选中了面包屑导航组件!虽然这个组件非常简单,但我认为它非常适合作为本系列教程的开篇。那么,让我们直接进入正题吧!

设置

如果您已经搭建好了自己的 React 项目,可以跳过这部分。本部分面向希望使用全新项目学习本系列教程的读者。

  • npx create-react-app <project-name>在终端中运行
  • 删除src/App.css文件
  • 请将src/App.js文件中的代码替换为以下内容:
import React from "react";

const App = () => <div>Hello, world!</div>;

export default App;
Enter fullscreen mode Exit fullscreen mode
  • 重命名src/index.cssindex.scss
  • 将文件引用从 4 更新到src/index.js
  • 消除src/logo.svg
  • 运行应用程序(yarn startnpm start

现在,你应该会在浏览器中看到“编译失败”错误,这是因为我们还没有将该node-sass包添加到我们的项目中。

  • yarn add node-sassnpm install node-sass您目前一直在使用的终端中运行
  • 重新运行您的应用程序(yarn startnpm start

你的浏览器现在应该显示“Hello, world!”。一切就绪!

我的过程

  1. 请阅读 WAI-ARIA 创作规范文档。
  2. 创建一个最小的 React 组件,显示“Hello”字样。
  3. 使用必要的 HTML 元素完善 React 组件。
  4. 确定 React 组件需要哪些输入(props)。
  5. 将属性添加到组件
  6. 添加必要的 WAI-ARIA 角色、状态和属性
  7. 添加键盘交互
  8. 进行手动测试(例如,使用屏幕阅读器收听、使用键盘导航等)
  9. 添加自动化测试
  10. 编写文档

WAI-ARIA 创作实践文档

我们首先需要做的是阅读WAI-ARIA创作实践网页上关于该组件的可用文档。这个组件的内容并不多。

面包屑导航由指向当前页面及其父页面的链接列表组成,并按层级顺序排列。它可以帮助用户在网站或Web应用程序中快速找到所需位置。面包屑导航通常水平放置在页面主要内容之前。

这里无需添加键盘交互,因为默认情况下可以使用 Tab 键和 Shift+Tab 键来浏览链接。我们只需确保在组件中使用正确的 HTML 元素,并且还需要包含一个 ARIA 状态aria-current和一个 ARIA 属性。aria-label

一个极简的 React 组件

本系列博客文章将使用我在 GitLab 仓库中设置的文件结构a11y-components。它看起来大致如下:

src/
  components/
    Button/
    Dialog/
    Listbox/
    ...
  App.js
Enter fullscreen mode Exit fullscreen mode

让我们Breadcrumb在 下添加一个文件夹components。如果您已按照上面的设置部分操作,则需要创建该components文件夹并向其中添加一个index.js文件。然后,我们需要向 Breadcrumb 文件夹添加 5 个文件:

  • 面包屑.jsx
  • 面包屑导航模块.scss
  • 面包屑.test.js
  • index.js
  • README.md

面包屑.jsx

这个文件将包含我们所有的 React 代码。让我们从最简单的示例开始,检查一下我们的配置是否正确:

import React from "react";

const Breadcrumb = () => <h1>Breadcrumb works!</h1>;

export default Breadcrumb;
Enter fullscreen mode Exit fullscreen mode

面包屑导航模块.scss

这个文件将存放我们所有的 CSS 代码。我们先不在这里添加任何内容,等到开始构建组件时再添加。

面包屑.test.js

别忘了编写测试!测试不仅能确保组件按预期运行,还能确保你未来所做的更改不会破坏现有行为。我们会在组件完成后编写测试。

index.js

此文件用于导出面包屑组件所需的所有内容,以便在应用程序的其他地方使用。更复杂的组件可能在此文件中导出多个数据,但对于此组件,我们将保持简洁:

export { default as Breadcrumb } from "./Breadcrumb";
Enter fullscreen mode Exit fullscreen mode

README.md

这里我们将记录组件的信息。详细说明组件的用途和使用方法非常重要。我们将包含三个主要部分:属性、辅助功能和用法(示例)。组件完成后,请保存此文件。

测试一下

首先将以下内容添加到src/components/index.js文件中:

export { Breadcrumb } from "./Breadcrumb";
Enter fullscreen mode Exit fullscreen mode

然后更新src/App.js以使用该组件:

import React from "react";

import { Breadcrumb } from "./components";

const App = () => <Breadcrumb />;

export default App;
Enter fullscreen mode Exit fullscreen mode

检查您的浏览器。它应该显示“面包屑导航有效!”,并带有一个<h1>元素。

向 React 组件添加 HTML 元素

现在我们的组件所有文件都已创建完毕,并且我们已经有了可以在浏览器中运行和显示的最小版本,我们可以开始按照规范进行扩展了。让我们回到文档,看看需要使用哪些元素。你应该会看到该组件的“示例”部分以及指向示例的链接。我们点击链接查看

在“辅助功能”部分,我们可以看到我们需要一个<nav>元素来包含所有链接,并且这些链接需要以有序列表(<ol>)组件的形式组织。暂时不用担心元素的标签问题,我们稍后会讨论这个问题。

我们先来修改面包屑导航组件的渲染内容。现在我们可以先将元素硬编码,然后在下一步中使组件更加动态。

<nav>
  <ol>
    <li>
      <a href="">Link 1</a>
    </li>
    <li>
      <a href="">Link 2</a>
    </li>
    <li>
      <a href="">Link 3</a>
    </li>
  </ol>
</nav>
Enter fullscreen mode Exit fullscreen mode

保存组件后,您应该会在浏览器中看到类似以下内容:

1. Link 1
2. Link 2
3. Link 3
Enter fullscreen mode Exit fullscreen mode

太棒了!现在我们需要将列表横向排列,并在每个链接之间添加分隔符。我们将使用 CSS 来实现这一点,这样屏幕阅读器就不会读取这些分隔符并将其显示给用户。

  • 将 SCSS 文件导入Breadcrumb.jsx
import styles from "./Breadcrumb.module.scss";
Enter fullscreen mode Exit fullscreen mode
  • nav组件中的元素 a className
<nav className={styles.BreadcrumbContainer}>...</nav>
Enter fullscreen mode Exit fullscreen mode
  • 将代码添加到Breadcrumb.module.scss
.BreadcrumbContainer {
  padding: 12px;
  background-color: lightgray;
  text-align: left;

  ol {
    margin: 0;
    padding: 0;
    list-style: none;

    li {
      display: inline;
      margin: 0;
      padding: 0;

      a {
        color: black;
      }
    }
  }

  // The visual separators
  li + li::before {
    display: inline-block;
    margin: 0 12px;
    transform: rotate(15deg);
    border-right: 2px solid black;
    height: 0.8em;
    content: "";
  }
}
Enter fullscreen mode Exit fullscreen mode

链接应水平排列在灰色背景上,每个链接之间用分隔符隔开。

向 React 组件添加属性

让我们让组件接受一个链接列表,这样它就具有动态性和可重用性。看起来每个链接都包含两部分:一个易读的标签和一个链接href。我们首先需要更新src/App.js链接数组并将其传递给组件,如下所示:

<Breadcrumb
  links={[
    {
      label: "Link 1",
      href: "",
    },
    {
      label: "Link 2",
      href: "",
    },
    {
      label: "Link 3",
      href: "",
    },
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

现在我们需要更新组件以接受和使用一个名为 的 prop links

const Breadcrumb = ({ links }) => (
  <nav className={styles.BreadcrumbContainer}>
    <ol>
      {links.map(link => (
        <li>
          <a href={link.href}>{link.label}</a>
        </li>
      ))}
    </ol>
  </nav>
);
Enter fullscreen mode Exit fullscreen mode

如果您使用的是之前硬编码的相同链接,那么当您查看浏览器时,它应该看起来与此步骤之前完全相同。

WAI-ARIA 角色、状态和属性

我们需要讨论此组件的两个 ARIA 属性:aria-labelaria-current

aria-label

此属性描述组件提供的导航类型。必须将其设置为“面包屑导航”,如下所示:

<nav aria-label="Breadcrumb">...</nav>
Enter fullscreen mode Exit fullscreen mode

您可以在这里aria-label阅读更多关于该房产的信息

aria-current

此属性应用于列表中的最后一个链接,使其显示为当前页面的链接。我们可以通过使用传递给回调函数的第二个参数来实现这一点,该map参数是数组中当前元素的索引。如果我们查看的索引比索引长度小 1,则表示我们正在查看数组中的最后一个元素,需要将该aria-current="page"属性应用于<a>我们正在渲染的元素。否则,该属性应为空undefined。以下是<ol>元素现在应该的样子:

<ol>
  {links.map((link, index) => {
    const isLastLink = index === links.length - 1;
    return (
      <li>
        <a href={link.href} aria-current={isLastLink ? "page" : undefined}>
          {link.label}
        </a>
      </li>
    );
  })}
</ol>
Enter fullscreen mode Exit fullscreen mode

我们可能还需要对当前页面的链接进行样式设置,以表明这是我们当前所在的页面。我们可以在 SCSS 文件中通过选择aria-current属性来实现这一点。您需要将以下代码添加到ol文件的 `<head>` 部分:

[aria-current="page"] {
  font-weight: bold;
  text-decoration: none;
}
Enter fullscreen mode Exit fullscreen mode

您可以点击此处aria-current阅读更多关于该州的信息

添加键盘交互

我们不需要为这个组件添加任何键盘交互!我们只需要确保 Tab 键和 Tab+Shift 键能够按预期与<a>元素交互即可。

执行手动测试

我使用ChromeVox Classic 扩展程序进行屏幕阅读器测试。只需chrome://extensions/在浏览器中打开并切换扩展程序的开关,即可轻松地仅在需要测试时启用或禁用它。

这里有一个视频,展示了当你使用 Tab 键切换该组件时,它的外观和声音会是什么样子:

添加自动化测试

由于该组件不涉及交互或状态变化,因此其测试应该非常简单。我们不需要测试点击事件,也不需要进行任何计算或其他类似操作。该组件仅负责加载和显示内容,这意味着我们唯一需要测试的就是加载时所有内容是否正确显示。我们将使用JestEnzyme进行测试。

酶的设置

首先,我们需要安装和配置 Enzyme。如果您已经安装并配置好了,可以跳到下一节。

  1. npm i --save-dev enzyme enzyme-adapter-react-16在终端中运行命令,使用 npm 安装 Enzyme。

  2. 在文件末尾添加以下代码setupTests.js以配置 Enyzme:

import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure({ adapter: new Adapter() });
Enter fullscreen mode Exit fullscreen mode

编写测试

由于文件很短,我现在就把它粘贴过来,然后逐步解释一下发生了什么。

import React from "react";
import { shallow } from "enzyme";

import Breadcrumb from "./Breadcrumb";

const testLinks = [
  { label: "Test Link 1", href: "test-link-1" },
  { label: "Test Link 2", href: "test-link-2" },
];

describe("<Breadcrumb />", () => {
  it("renders successfully with the correct aria attributes", () => {
    const wrapper = shallow(<Breadcrumb links={testLinks} />);

    const nav = wrapper.find("nav");
    expect(nav).toHaveLength(1);
    expect(nav.props()["aria-label"]).toBe("Breadcrumb");

    const anchorElements = wrapper.find("a");
    expect(anchorElements).toHaveLength(testLinks.length);

    const firstAnchor = anchorElements.first();
    expect(firstAnchor.text()).toBe(testLinks[0].label);
    expect(firstAnchor.props()["href"]).toBe(testLinks[0].href);

    const lastAnchor = anchorElements.last();
    expect(lastAnchor.props()["aria-current"]).toBe("page");
  });
});
Enter fullscreen mode Exit fullscreen mode

导入所有必要的库之后,我们就得到了一个links常量,它保​​存着执行测试所需的测试值。将测试值存储起来而不是直接硬编码到代码中是一种良好的实践,原因与我们在其他代码中不建议这样做的原因相同:这样更容易修改测试值。在几百行代码的测试文件中更新一堆字符串可不是什么轻松的事。在测试中使用变量真是太方便了!

接下来是主describe代码块,它包含了该组件的所有测试。我们有一个单独的it代码块(是 `<test>` 的别名test),用于运行我们唯一的测试。在测试中,我们可以调用任意数量的expect`<test>`。这里有很多 `<test>`,让我们看看每个 `<test>` 测试的内容。

  1. 首先,我们对组件进行浅渲染。这是 Enzyme 的一个概念,您可以通过此链接阅读相关内容及其 API 参考文档。

  2. 我们对组件的要求之一是,它必须将所有内容包裹在一个<nav>元素中,并且该元素必须具有 ` aria-label="Breadcrumb"on` 属性。我们通过 `.` 来测试这一点find。我们只希望只有一个元素,所以第一个 `expect` 就是为了实现这一点。然后,我们想要检查 ` propson` 属性nav,并确保 ` aria-labelprop` 已正确设置为 `true` "Breadcrumb"

  3. 接下来,我们要确保根据组件通过 prop 传递的输入,渲染正确数量的锚点元素links。与上一步类似,我们遍历find所有<a>元素,然后期望找到的元素数量与testLinks数组中的元素数量一致。

  4. 现在我们可以查看渲染的第一个链接,确保它的 `<a>`label和`<link>` 元素都href正确渲染。我们使用便捷的first方法获取第一个锚元素。然后,我们检查它的 `<a>` 元素text是否与第一个测试链接的 `<link>` 元素匹配label。最后,我们检查props该元素的 `<link>` 元素,确保href其设置为测试链接的 `<link>` 元素href注意:我们只需要对第一个元素执行这些检查,因为如果第一个元素渲染正确,那么其他元素也都会正确渲染。

  5. 最后,我们需要确保最后一个锚元素的aria-current属性设置为 `true` "page"。没错!Enzyme 也提供了一个last相应的方法first。类似于我们aria-label在步骤 2 中检查 `prop` 的方式,我们期望它的字符串值为 `true` "page"

编写文档

我们快完成了!让我们把文档写好,然后就可以欣赏我们漂亮的全新组件了。

  • 打开面包屑导航README.md,添加 H1 标题和组件的描述/用途。
# Breadcrumb

This component displays a list of links to show users where they are within an application.
Enter fullscreen mode Exit fullscreen mode
  • 添加一个 H2 标题“属性”。在这里,我们将描述传递给组件的属性。这些属性应该以表格的形式存在于你的文件中,但为了便于格式化,我将它们列在下面作为列表。
## Properties

**Links**

- Type: Array
- Required: Yes
- Default value: None
- Description: These are the links to show in the breadcrumb. Each has a `label` and an `href` attribute.
Enter fullscreen mode Exit fullscreen mode
  • 添加另一个 H2 标题“辅助功能”。我们将详细介绍键盘交互、WAI-ARIA 角色、状态和属性以及其他功能,就像 WAI-ARIA 网站那样。
## Accessibility

### Keyboard Interaction

Not applicable.

### WAI-ARIA Roles, States, and Properties

- The links are contained in an ordered list within a `<nav>` element
- The `<nav>` element has the `aria-label` attribute set to `"Breadcrumb"`
- The last link in the list represents the current page, and must have `aria-current` set to `"page"`

### Additional Features

- The separators between each link are added via CSS so they are not presented by a screen reader
Enter fullscreen mode Exit fullscreen mode
  • 最后,我们添加一个 H2 标题“用法”。这里我们将放置一些组件使用方法的代码示例。
## Usage

<Breadcrumb
  links={[
    { label: "Link 1", href: "" },
    { label: "Link 2", href: "" },
    { label: "Link 3", href: "" }
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

结论

就这样!我们已经有了一个易于使用的面包屑导航组件。每周二晚上7点(美国东部时间),欢迎来我的Twitch频道观看直播编程!我们还有很多易于使用的React组件要开发,到目前为止,整个过程都非常有趣。记得关注我的频道,这样每次直播你都会收到通知!


你知道我有电子报吗?📬

如果您想在我发布新博客文章或发布重大项目公告时收到通知,请访问https://ashleemboyer.com/newsletter

文章来源:https://dev.to/ashleemboyer/build-an-accessible-react-component-part-1-breadcrumbs-259o