构建易于访问的 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;
- 重命名
src/index.css为index.scss - 将文件引用从 4 更新到
src/index.js - 消除
src/logo.svg - 运行应用程序(
yarn start或npm start)
现在,你应该会在浏览器中看到“编译失败”错误,这是因为我们还没有将该node-sass包添加到我们的项目中。
yarn add node-sass在npm install node-sass您目前一直在使用的终端中运行- 重新运行您的应用程序(
yarn start或npm start)
你的浏览器现在应该显示“Hello, world!”。一切就绪!
我的过程
- 请阅读 WAI-ARIA 创作规范文档。
- 创建一个最小的 React 组件,显示“Hello”字样。
- 使用必要的 HTML 元素完善 React 组件。
- 确定 React 组件需要哪些输入(props)。
- 将属性添加到组件
- 添加必要的 WAI-ARIA 角色、状态和属性
- 添加键盘交互
- 进行手动测试(例如,使用屏幕阅读器收听、使用键盘导航等)
- 添加自动化测试
- 编写文档
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
让我们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;
面包屑导航模块.scss
这个文件将存放我们所有的 CSS 代码。我们先不在这里添加任何内容,等到开始构建组件时再添加。
面包屑.test.js
别忘了编写测试!测试不仅能确保组件按预期运行,还能确保你未来所做的更改不会破坏现有行为。我们会在组件完成后编写测试。
index.js
此文件用于导出面包屑组件所需的所有内容,以便在应用程序的其他地方使用。更复杂的组件可能在此文件中导出多个数据,但对于此组件,我们将保持简洁:
export { default as Breadcrumb } from "./Breadcrumb";
README.md
这里我们将记录组件的信息。详细说明组件的用途和使用方法非常重要。我们将包含三个主要部分:属性、辅助功能和用法(示例)。组件完成后,请保存此文件。
测试一下
首先将以下内容添加到src/components/index.js文件中:
export { Breadcrumb } from "./Breadcrumb";
然后更新src/App.js以使用该组件:
import React from "react";
import { Breadcrumb } from "./components";
const App = () => <Breadcrumb />;
export default App;
检查您的浏览器。它应该显示“面包屑导航有效!”,并带有一个<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>
保存组件后,您应该会在浏览器中看到类似以下内容:
1. Link 1
2. Link 2
3. Link 3
太棒了!现在我们需要将列表横向排列,并在每个链接之间添加分隔符。我们将使用 CSS 来实现这一点,这样屏幕阅读器就不会读取这些分隔符并将其显示给用户。
- 将 SCSS 文件导入
Breadcrumb.jsx:
import styles from "./Breadcrumb.module.scss";
- 给
nav组件中的元素 aclassName:
<nav className={styles.BreadcrumbContainer}>...</nav>
- 将代码添加到
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: "";
}
}
链接应水平排列在灰色背景上,每个链接之间用分隔符隔开。
向 React 组件添加属性
让我们让组件接受一个链接列表,这样它就具有动态性和可重用性。看起来每个链接都包含两部分:一个易读的标签和一个链接href。我们首先需要更新src/App.js链接数组并将其传递给组件,如下所示:
<Breadcrumb
links={[
{
label: "Link 1",
href: "",
},
{
label: "Link 2",
href: "",
},
{
label: "Link 3",
href: "",
},
]}
/>
现在我们需要更新组件以接受和使用一个名为 的 prop links。
const Breadcrumb = ({ links }) => (
<nav className={styles.BreadcrumbContainer}>
<ol>
{links.map(link => (
<li>
<a href={link.href}>{link.label}</a>
</li>
))}
</ol>
</nav>
);
如果您使用的是之前硬编码的相同链接,那么当您查看浏览器时,它应该看起来与此步骤之前完全相同。
WAI-ARIA 角色、状态和属性
我们需要讨论此组件的两个 ARIA 属性:aria-label和aria-current。
aria-label
此属性描述组件提供的导航类型。必须将其设置为“面包屑导航”,如下所示:
<nav aria-label="Breadcrumb">...</nav>
您可以在这里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>
我们可能还需要对当前页面的链接进行样式设置,以表明这是我们当前所在的页面。我们可以在 SCSS 文件中通过选择aria-current属性来实现这一点。您需要将以下代码添加到ol文件的 `<head>` 部分:
[aria-current="page"] {
font-weight: bold;
text-decoration: none;
}
您可以点击此处aria-current阅读更多关于该州的信息。
添加键盘交互
我们不需要为这个组件添加任何键盘交互!我们只需要确保 Tab 键和 Tab+Shift 键能够按预期与<a>元素交互即可。
执行手动测试
我使用ChromeVox Classic 扩展程序进行屏幕阅读器测试。只需chrome://extensions/在浏览器中打开并切换扩展程序的开关,即可轻松地仅在需要测试时启用或禁用它。
这里有一个视频,展示了当你使用 Tab 键切换该组件时,它的外观和声音会是什么样子:
添加自动化测试
由于该组件不涉及交互或状态变化,因此其测试应该非常简单。我们不需要测试点击事件,也不需要进行任何计算或其他类似操作。该组件仅负责加载和显示内容,这意味着我们唯一需要测试的就是加载时所有内容是否正确显示。我们将使用Jest和Enzyme进行测试。
酶的设置
首先,我们需要安装和配置 Enzyme。如果您已经安装并配置好了,可以跳到下一节。
-
npm i --save-dev enzyme enzyme-adapter-react-16在终端中运行命令,使用 npm 安装 Enzyme。 -
在文件末尾添加以下代码
setupTests.js以配置 Enyzme:
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
编写测试
由于文件很短,我现在就把它粘贴过来,然后逐步解释一下发生了什么。
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");
});
});
导入所有必要的库之后,我们就得到了一个links常量,它保存着执行测试所需的测试值。将测试值存储起来而不是直接硬编码到代码中是一种良好的实践,原因与我们在其他代码中不建议这样做的原因相同:这样更容易修改测试值。在几百行代码的测试文件中更新一堆字符串可不是什么轻松的事。在测试中使用变量真是太方便了!
接下来是主describe代码块,它包含了该组件的所有测试。我们有一个单独的it代码块(是 `<test>` 的别名test),用于运行我们唯一的测试。在测试中,我们可以调用任意数量的expect`<test>`。这里有很多 `<test>`,让我们看看每个 `<test>` 测试的内容。
-
首先,我们对组件进行浅渲染。这是 Enzyme 的一个概念,您可以通过此链接阅读相关内容及其 API 参考文档。
-
我们对组件的要求之一是,它必须将所有内容包裹在一个
<nav>元素中,并且该元素必须具有 `aria-label="Breadcrumb"on` 属性。我们通过 `.` 来测试这一点find。我们只希望只有一个元素,所以第一个 `expect` 就是为了实现这一点。然后,我们想要检查 `propson` 属性nav,并确保 `aria-labelprop` 已正确设置为 `true`"Breadcrumb"。 -
接下来,我们要确保根据组件通过 prop 传递的输入,渲染正确数量的锚点元素
links。与上一步类似,我们遍历find所有<a>元素,然后期望找到的元素数量与testLinks数组中的元素数量一致。 -
现在我们可以查看渲染的第一个链接,确保它的 `<a>`
label和`<link>` 元素都href正确渲染。我们使用便捷的first方法获取第一个锚元素。然后,我们检查它的 `<a>` 元素text是否与第一个测试链接的 `<link>` 元素匹配label。最后,我们检查props该元素的 `<link>` 元素,确保href其设置为测试链接的 `<link>` 元素href。注意:我们只需要对第一个元素执行这些检查,因为如果第一个元素渲染正确,那么其他元素也都会正确渲染。 -
最后,我们需要确保最后一个锚元素的
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.
- 添加一个 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.
- 添加另一个 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
- 最后,我们添加一个 H2 标题“用法”。这里我们将放置一些组件使用方法的代码示例。
## Usage
<Breadcrumb
links={[
{ label: "Link 1", href: "" },
{ label: "Link 2", href: "" },
{ label: "Link 3", href: "" }
]}
/>
结论
就这样!我们已经有了一个易于使用的面包屑导航组件。每周二晚上7点(美国东部时间),欢迎来我的Twitch频道观看直播编程!我们还有很多易于使用的React组件要开发,到目前为止,整个过程都非常有趣。记得关注我的频道,这样每次直播你都会收到通知!
你知道我有电子报吗?📬
如果您想在我发布新博客文章或发布重大项目公告时收到通知,请访问https://ashleemboyer.com/newsletter。
文章来源:https://dev.to/ashleemboyer/build-an-accessible-react-component-part-1-breadcrumbs-259o