就业培训     下载中心     Wiki     联络
登录   注册

Log
  1. 首页
  2. 学习 Web 开发
  3. Tools and testing
  4. 理解客户端侧 JavaScript 框架
  5. Accessibility in React

内容表

  • Including keyboard users
  • Exploring the keyboard usability problem
  • Focusing between templates
  • More robust focus management
  • Focusing when the user deletes a task
  • Finished!
  • In this module

Accessibility in React

  • 上一
  • Overview: Client-side JavaScript frameworks
  • 下一

In our final tutorial article, we'll focus on (pun intended) accessibility, including focus management in React, which can improve usability and reduce confusion for both keyboard-only and screenreader users.

Prerequisites: Familiarity with the core HTML , CSS ,和 JavaScript languages, knowledge of the terminal/command line .
Objective: To learn about implementing keyboard accessibility in React.

Including keyboard users

At this point, we've accomplished all of the features we set out to implement. A user can add a new task, check and uncheck tasks, delete tasks, or edit task names. And they can filter their task list by all, active, or completed tasks.

Or, at least: they can do all of these things with a mouse. Unfortunately, these features are not very accessible to keyboard-only users. Let's explore this now.

Exploring the keyboard usability problem

Start by clicking on the input at the top of our app, as if you're going to add a new task. You'll see a thick, dashed outline around that input. This outline is your visual indicator that the browser is currently focused on this element. Press the Tab key, and you will see the outline appear around the "Add" button beneath the input. This shows you that the browser's focus has moved.

Press Tab a few more times, and you will see this dashed focus indicator move between each of the filter buttons. Keep going until the focus indicator is around the first "Edit" button. Press Enter .

<Todo /> component will switch templates, as we designed, and you'll see a form that lets us edit the name of the task.

But where did our focus indicator go?

When we switch between templates in our <Todo /> component, we completely remove the elements that were there before to replace them with something else. That means the element that we were focused on vanishes, and nothing is in focus at all. This could confuse a wide variety of users — particularly users who rely on the keyboard, or users who use a screen reader.

To improve the experience for keyboard and screen-reader users, we should manage the browser’s focus ourselves.

Focusing between templates

When a user toggles a <Todo/> template from viewing to editing, we should focus on the <input> used to rename it; when they toggle back from editing to viewing, we should move focus back to the "Edit" button.

Targeting our elements

In order to focus on an element in our DOM, we need to tell React which element we want to focus on and how to find it. React’s useRef hook creates an object with a single property: current . This property can be a reference to anything we want and look that reference up later. It’s particularly useful for referring to DOM elements.

Change the import statement at the top of Todo.js so that it includes useRef :

import React, { useRef, useState } from "react";

								

Then, create two new constants beneath the hooks in your Todo() function. Each should be a ref – one for the "Edit" button in the view template and one for the edit field in the editing template.

const editFieldRef = useRef(null);
const editButtonRef = useRef(null);

								

These refs have a default value of null because they will not have value until we attach them to their respective elements. To do that, we'll add an attribute of ref to each element, and set their values to the appropriately named ref 对象。

The textbox <input> in your editing template should be updated like this:

<input
  id={props.id}
  className="todo-text"
  type="text"
  value={newName}
  onChange={handleChange}
  ref={editFieldRef}
/>

								

The "Edit" button in your view template should read like this:

<button
  type="button"
  className="btn"
  onClick={() => setEditing(true)}
  ref={editButtonRef}
>
  Edit <span className="visually-hidden">{props.name}</span>
</button>

								

Focusing on our refs with useEffect

To use our refs for their intended purpose, we need to import another React hook: useEffect() . useEffect() is so named because it runs after React renders a given component, and will run any side-effects that we'd like to add to the render process, which we can't run inside the main function body. useEffect() is useful in the current situation because we cannot focus on an element until after the <Todo /> component renders and React knows where our refs are.

Change the import statement of Todo.js again to add useEffect :

import React, { useEffect, useRef, useState } from "react";

								

useEffect() takes a function as an argument; this function is executed after the component renders. Let's see this in action; put the following useEffect() call just above the return statement in the body of Todo() , and pass into it a function that logs the words "side effect" to your console:

useEffect(() => {
  console.log("side effect");
});

								

To illustrate the difference between the main render process and code run inside useEffect() , add another log – put this one below the previous addition:

console.log("main render");

								

Now, open the app in your browser. You should see both messages in your console, with each one repeating three times. Note how "main render" logged first, and "side effect" logged second, even though the "side effect" log appears first in the code.

main render (3)                                     Todo.js:100
side effect (3)                                     Todo.js:98
								

That's it for our experimentation for now. Delete console.log("main render") now, and lets move on to implementing our focus management.

Focusing on our editing field

Now that we know our useEffect() hook works, we can manage focus with it. As a reminder, we want to focus on the editing field when we switch to the editing template.

Update your existing useEffect() hook so that it reads like this:

useEffect(() => {
  if (isEditing) {
    editFieldRef.current.focus();
  }
}, [isEditing]);

								

These changes make it so that, if isEditing is true, React reads the current value of the editFieldRef and moves browser focus to it. We also pass an array into useEffect() as a second argument. This array is a list of values useEffect() should depend on. With these values included, useEffect() will only run when one of those values changes. We only want to change focus when the value of isEditing 改变。

Try it now, and you'll see that when you click an "Edit" button, focus moves to the corresponding edit <input> !

Moving focus back to the edit button

At first glance, getting React to move focus back to our "Edit" button when the edit is saved or cancelled appears deceptively easy. Surely we could add a condition to our useEffect to focus on the edit button if isEditing is false ? Let's try it now — update your useEffect() call like so:

useEffect(() => {
  if (isEditing) {
    editFieldRef.current.focus();
  } else {
    editButtonRef.current.focus();
  }
}, [isEditing]);

								

This kind of mostly works. Head back to your browser and you’ll see that your focus moves between Edit <input> and "Edit" button as you start and end an edit. However, you may have noticed a new problem — the "Edit" button in the final <Todo /> component is focussed immediately on page load, before we even interact with the app!

我们的 useEffect() hook is behaving exactly as we designed it: it runs as soon as the component renders, sees that isEditing is false , and focuses the "Edit" button. Because there are three instances of <Todo /> , we see focus on the last "Edit" button.

We need to refactor our approach so that focus changes only when isEditing changes from one value to another.

More robust focus management

In order to meet our refined criteria, we need to know not just the value of isEditing , but also when that value has changed . In order to do that, we need to be able to read the previous value of the isEditing constant. Using pseudocode, our logic should be something like this:

if (wasNotEditingBefore && isEditingNow) {
  focusOnEditField()
}
if (wasEditingBefore && isNotEditingNow) {
  focusOnEditButton()
}
								

The React team had discussed ways to get a component’s previous state , and has provided an example custom hook we can use for the job.

Paste the following code near the top of Todo.js , above your Todo() 函数。

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

								

Now we'll define a wasEditing constant beneath the hooks at the top of Todo() . We want this constant to track the previous value of isEditing , so we call usePrevious with isEditing as an argument:

const wasEditing = usePrevious(isEditing);

								

With this constant, we can update our useEffect() hook to implement the pseudocode we discussed before — update it as follows:

useEffect(() => {
  if (!wasEditing && isEditing) {
    editFieldRef.current.focus();
  }
  if (wasEditing && !isEditing) {
    editButtonRef.current.focus();
  }
}, [wasEditing, isEditing]);

								

Note that the logic of useEffect() now depends on wasEditing , so we provide it in the array of dependencies.

Again try using the "Edit" and "Cancel" buttons to toggle between the templates of your <Todo /> component; you'll see the browser focus indicator move appropriately, without the problem we discussed at the start of this section.

Focusing when the user deletes a task

There's one last keyboard experience gap: when a user deletes a task from the list, the focus vanishes. We're going to follow a pattern similar to our previous changes: we'll make a new ref, and utilize our usePrevious() hook, so that we can focus on the list heading whenever a user deletes a task.

Why the list heading?

Sometimes, the place we want to send our focus to is obvious: when we toggled our <Todo /> templates, we had an origin point to "go back" to — the "Edit" button. In this case however, since we're completely removing elements from the DOM, we have no place to go back to. The next best thing is an intuitive location somewhere nearby. The list heading is our best choice because it's close to the list item the user will delete, and focusing on it will tell the user how many tasks are left.

Creating our ref

导入 useRef() and useEffect() hooks into App.js — you'll need them both below:

import React, { useState, useRef, useEffect } from "react";

								

Then declare a new ref inside the App() function. Just above the return statement is a good place:

const listHeadingRef = useRef(null);

								

Prepare the heading

Heading elements like our <h2> are not usually focusable. This isn't a problem — we can make any element programmatically focusable by adding the attribute tabindex="-1" to it. This means only focusable with JavaScript . You can't press Tab to focus on an element with a tabindex of -1 the same way you could do with a <button> or <a> element (this can be done using tabindex="0" , but that's not really appropriate in this case).

Let's add the tabindex attribute — written as tabIndex in JSX — to the heading above our list of tasks, along with our headingRef :

<h2 id="list-heading" tabIndex="-1" ref={listHeadingRef}>
  {headingText}
</h2>

								

注意: tabindex attribute is great for accessibility edge-cases, but you should take great care to not overuse it. Only apply a tabindex to an element when you're absolutely sure that making it focusable will benefit your user in some way. In most cases, you should be utilizing elements that can naturally take focus, such as buttons, anchors, and inputs. Irresponsible usage of tabindex could have a profoundly negative impact on keyboard and screen-reader users!

Getting previous state

We want to focus on the element associated with our ref (via the ref attribute) only when our user deletes a task from their list. That's going to require the usePrevious() hook we already used earlier on. Add it to the top of your App.js file, just below the imports:

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

								

Now add the following, above the return statement inside the App() 函数:

const prevTaskLength = usePrevious(tasks.length);

								

Here we are invoking usePrevious() to track the length of the tasks state, like so:

注意: Since we're now utilizing usePrevious() in two files, a good efficiency refactor would be to move the usePrevious() function into its own file, export it from that file, and import it where you need it. Try doing this as an exercise once you've got to the end.

使用 useEffect() to control our heading focus

Now that we've stored how many tasks we previously had, we can set up a useEffect() hook to run when our number of tasks changes, which will focus the heading if the number of tasks we have now is less than with it previously was — i.e. we deleted a task!

Add the following into the body of your App() function, just below your previous additions:

useEffect(() => {
  if (tasks.length - prevTaskLength === -1) {
    listHeadingRef.current.focus();
  }
}, [tasks.length, prevTaskLength]);

								

We only try to focus on our list heading if we have fewer tasks now than we did before. The dependencies passed into this hook ensure it will only try to re-run when either of those values (the number of current tasks, or the number of previous tasks) changes.

Now, when you delete a task in your browser, you will see our dashed focus outline appear around the heading above the list.

Finished!

You've just finished building a React app from the ground up! Congratulations! The skills you’ve learned here will be a great foundation to build on as you continue working with React.

Most of the time, you can be an effective contributor to a React project even if all you do is think carefully about components and their state and props. Remember to always write the best HTML you can.

useRef() and useEffect() are somewhat advanced features, and you should be proud of yourself for using them! Look out for opportunities to practice them more, because doing so will allow you to create inclusive experiences for users. Remember: our app would have been inaccessible to keyboard users without them!

注意: If you need to check your code against our version, you can find a finished version of the sample React app code in our todo-react repository . For a running live version, see https://mdn.github.io/todo-react-build/ .

In the very last article we'll present you with a list of React resources that you can use to go further in your learning.

  • 上一
  • Overview: Client-side JavaScript frameworks
  • 下一

In this module

  • Introduction to client-side frameworks
  • Framework main features
  • React
    • Getting started with React
    • Beginning our React todo list
    • Componentizing our React app
    • React interactivity: Events and state
    • React interactivity: Editing, filtering, conditional rendering
    • Accessibility in React
    • React resources
  • Ember
    • Getting started with Ember
    • Ember app structure and componentization
    • Ember interactivity: Events, classes and state
    • Ember Interactivity: Footer functionality, conditional rendering
    • Routing in Ember
    • Ember resources and troubleshooting
  • Vue
    • Getting started with Vue
    • Creating our first Vue component
    • Rendering a list of Vue components
    • Adding a new todo form: Vue events, methods, and models
    • Styling Vue components with CSS
    • Using Vue computed properties
    • Vue conditional rendering: editing existing todos
    • Focus management with Vue refs
    • Vue resources
  • Svelte
    • Getting started with Svelte
    • Starting our Svelte Todo list app
    • Dynamic behavior in Svelte: working with variables and props
    • Componentizing our Svelte app
    • Advanced Svelte: Reactivity, lifecycle, accessibility
    • Working with Svelte stores
    • TypeScript support in Svelte
    • Deployment and next steps
  • Angular
    • Getting started with Angular
    • Beginning our Angular todo list app
    • Styling our Angular app
    • Creating an item component
    • Filtering our to-do items
    • Building Angular applications and further resources

发现此页面有问题吗?

  • 编辑在 GitHub
  • 源在 GitHub
  • Report a problem with this content on GitHub
  • 想要自己修复问题吗?见 我们的贡献指南 .

最后修改: Jan 22, 2022 , 由 MDN 贡献者

相关话题

  1. Complete beginners start here!
  2. Web 快速入门
    1. Getting started with the Web overview
    2. 安装基本软件
    3. What will your website look like?
    4. 处理文件
    5. HTML 基础
    6. CSS 基础
    7. JavaScript 基础
    8. 发布您的网站
    9. How the Web works
  3. HTML — Structuring the Web
  4. HTML 介绍
    1. Introduction to HTML overview
    2. Getting started with HTML
    3. What's in the head? Metadata in HTML
    4. HTML text fundamentals
    5. Creating hyperlinks
    6. Advanced text formatting
    7. Document and website structure
    8. Debugging HTML
    9. Assessment: Marking up a letter
    10. Assessment: Structuring a page of content
  5. 多媒体和嵌入
    1. Multimedia and embedding overview
    2. Images in HTML
    3. Video and audio content
    4. From object to iframe — other embedding technologies
    5. Adding vector graphics to the Web
    6. Responsive images
    7. Assessment: Mozilla splash page
  6. HTML 表格
    1. HTML tables overview
    2. HTML table basics
    3. HTML Table advanced features and accessibility
    4. Assessment: Structuring planet data
  7. CSS — Styling the Web
  8. CSS 第一步
    1. CSS first steps overview
    2. What is CSS?
    3. Getting started with CSS
    4. How CSS is structured
    5. How CSS works
    6. Using your new knowledge
  9. CSS 构建块
    1. CSS building blocks overview
    2. Cascade and inheritance
    3. CSS 选择器
    4. The box model
    5. Backgrounds and borders
    6. Handling different text directions
    7. Overflowing content
    8. Values and units
    9. Sizing items in CSS
    10. Images, media, and form elements
    11. Styling tables
    12. Debugging CSS
    13. Organizing your CSS
  10. 样式化文本
    1. Styling text overview
    2. Fundamental text and font styling
    3. Styling lists
    4. Styling links
    5. Web fonts
    6. Assessment: Typesetting a community school homepage
  11. CSS 布局
    1. CSS layout overview
    2. Introduction to CSS layout
    3. Normal Flow
    4. Flexbox
    5. Grids
    6. Floats
    7. 位置
    8. Multiple-column Layout
    9. Responsive design
    10. Beginner's guide to media queries
    11. Legacy Layout Methods
    12. Supporting Older Browsers
    13. Fundamental Layout Comprehension
  12. JavaScript — Dynamic client-side scripting
  13. JavaScript 第一步
    1. JavaScript first steps overview
    2. What is JavaScript?
    3. A first splash into JavaScript
    4. What went wrong? Troubleshooting JavaScript
    5. Storing the information you need — Variables
    6. Basic math in JavaScript — Numbers and operators
    7. Handling text — Strings in JavaScript
    8. Useful string methods
    9. 数组
    10. Assessment: Silly story generator
  14. JavaScript 构建块
    1. JavaScript building blocks overview
    2. Making decisions in your code — Conditionals
    3. Looping code
    4. Functions — Reusable blocks of code
    5. Build your own function
    6. Function return values
    7. 事件介绍
    8. Assessment: Image gallery
  15. 引入 JavaScript 对象
    1. Introducing JavaScript objects overview
    2. Object basics
    3. 对象原型
    4. Object-oriented programming concepts
    5. Classes in JavaScript
    6. Working with JSON data
    7. Object building practice
    8. Assessment: Adding features to our bouncing balls demo
  16. 异步 JavaScript
    1. Asynchronous JavaScript overview
    2. General asynchronous programming concepts
    3. Introducing asynchronous JavaScript
    4. Cooperative asynchronous Java​Script: Timeouts and intervals
    5. Graceful asynchronous programming with Promises
    6. Making asynchronous programming easier with async and await
    7. Choosing the right approach
  17. 客户端侧 Web API
    1. 客户端侧 Web API
    2. Introduction to web APIs
    3. Manipulating documents
    4. Fetching data from the server
    5. Third party APIs
    6. Drawing graphics
    7. Video and audio APIs
    8. Client-side storage
  18. Web forms — Working with user data
  19. Core forms learning pathway
    1. Web forms overview
    2. Your first form
    3. How to structure a web form
    4. Basic native form controls
    5. The HTML5 input types
    6. Other form controls
    7. Styling web forms
    8. Advanced form styling
    9. UI pseudo-classes
    10. Client-side form validation
    11. Sending form data
  20. Advanced forms articles
    1. How to build custom form controls
    2. Sending forms through JavaScript
    3. CSS property compatibility table for form controls
  21. Accessibility — Make the web usable by everyone
  22. Accessibility guides
    1. Accessibility overview
    2. What is accessibility?
    3. HTML: A good basis for accessibility
    4. CSS and JavaScript accessibility best practices
    5. WAI-ARIA basics
    6. Accessible multimedia
    7. Mobile accessibility
  23. Accessibility assessment
    1. Assessment: Accessibility troubleshooting
  24. Tools and testing
  25. Client-side web development tools
    1. Client-side web development tools index
    2. Client-side tooling overview
    3. Command line crash course
    4. Package management basics
    5. Introducing a complete toolchain
    6. Deploying our app
  26. Introduction to client-side frameworks
    1. Client-side frameworks overview
    2. Framework main features
  27. React
    1. Getting started with React
    2. Beginning our React todo list
    3. Componentizing our React app
    4. React interactivity: Events and state
    5. React interactivity: Editing, filtering, conditional rendering
    6. Accessibility in React
    7. React resources
  28. Ember
    1. Getting started with Ember
    2. Ember app structure and componentization
    3. Ember interactivity: Events, classes and state
    4. Ember Interactivity: Footer functionality, conditional rendering
    5. Routing in Ember
    6. Ember resources and troubleshooting
  29. Vue
    1. Getting started with Vue
    2. Creating our first Vue component
    3. Rendering a list of Vue components
    4. Adding a new todo form: Vue events, methods, and models
    5. Styling Vue components with CSS
    6. Using Vue computed properties
    7. Vue conditional rendering: editing existing todos
    8. Focus management with Vue refs
    9. Vue resources
  30. Svelte
    1. Getting started with Svelte
    2. Starting our Svelte Todo list app
    3. Dynamic behavior in Svelte: working with variables and props
    4. Componentizing our Svelte app
    5. Advanced Svelte: Reactivity, lifecycle, accessibility
    6. Working with Svelte stores
    7. TypeScript support in Svelte
    8. Deployment and next steps
  31. Angular
    1. Getting started with Angular
    2. Beginning our Angular todo list app
    3. Styling our Angular app
    4. Creating an item component
    5. Filtering our to-do items
    6. Building Angular applications and further resources
  32. Git and GitHub
    1. Git and GitHub overview
    2. Hello World
    3. Git Handbook
    4. Forking Projects
    5. About pull requests
    6. Mastering Issues
  33. Cross browser testing
    1. Cross browser testing overview
    2. Introduction to cross browser testing
    3. Strategies for carrying out testing
    4. Handling common HTML and CSS problems
    5. Handling common JavaScript problems
    6. Handling common accessibility problems
    7. Implementing feature detection
    8. Introduction to automated testing
    9. Setting up your own test automation environment
  34. Server-side website programming
  35. 第一步
    1. First steps overview
    2. Introduction to the server-side
    3. Client-Server overview
    4. Server-side web frameworks
    5. Website security
  36. Django Web 框架 (Python)
    1. Django web framework (Python) overview
    2. 介绍
    3. 设置开发环境
    4. Tutorial: The Local Library website
    5. Tutorial Part 2: Creating a skeleton website
    6. Tutorial Part 3: Using models
    7. Tutorial Part 4: Django admin site
    8. Tutorial Part 5: Creating our home page
    9. Tutorial Part 6: Generic list and detail views
    10. Tutorial Part 7: Sessions framework
    11. Tutorial Part 8: User authentication and permissions
    12. Tutorial Part 9: Working with forms
    13. Tutorial Part 10: Testing a Django web application
    14. Tutorial Part 11: Deploying Django to production
    15. Web application security
    16. Assessment: DIY mini blog
  37. Express Web Framework (node.js/JavaScript)
    1. Express Web Framework (Node.js/JavaScript) overview
    2. Express/Node introduction
    3. Setting up a Node (Express) development environment
    4. Express tutorial: The Local Library website
    5. Express Tutorial Part 2: Creating a skeleton website
    6. Express Tutorial Part 3: Using a database (with Mongoose)
    7. Express Tutorial Part 4: Routes and controllers
    8. Express Tutorial Part 5: Displaying library data
    9. Express Tutorial Part 6: Working with forms
    10. Express Tutorial Part 7: Deploying to production
  38. Further resources
  39. Common questions
    1. HTML questions
    2. CSS questions
    3. JavaScript questions
    4. Web mechanics
    5. Tools and setup
    6. Design and accessibility

版权所有  © 2014-2026 乐数软件    

工业和信息化部: 粤ICP备14079481号-1