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

Log
  1. 首页
  2. 学习 Web 开发
  3. Tools and testing
  4. 理解客户端侧 JavaScript 框架
  5. Focus management with Vue refs

内容表

  • The focus management problem
  • Virtual DOM and refs
  • Vue's $nextTick() 方法
  • Vue lifecycle methods
  • Handling focus when deleting to-do items
  • 摘要
  • In this module

Focus management with Vue refs

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

We are nearly done with Vue. The last bit of functionality to look at is focus management, or put another way, how we can improve our app's keyboard accessibility. We'll look at using Vue refs to handle this — an advanced feature that allows you to have direct access to the underlying DOM nodes below the virtual DOM, or direct access from one component to the internal DOM structure of a child component.

Prerequisites: Familiarity with the core HTML , CSS ,和 JavaScript languages, knowledge of the terminal/command line .

Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.

Objective: To learn how to handle focus management using Vue refs.

The focus management problem

While we do have working edit functionality, we aren't providing a great experience for non-mouse users. Specifically, when a user activates the "Edit" Button, we remove the "Edit" Button from the DOM, but we don't move the user's focus anywhere, so in effect it just disappears. This can be disorienting for keyboard and non-visual users.

To understand what's currently happening:

  1. Reload your page, then press Tab . You should see a focus outline on the input for adding new to-do items.
  2. Press Tab again. The focus should move to the "Add" button.
  3. Hit it again, and it'll be on the first checkbox. One more time, and focus should be on the first "Edit" button.
  4. Activate the "Edit" button by pressing Enter . The checkbox will be replaced with our edit component, but the focus outline will be gone.

This behavior can be jarring. In addition, what happens when you press Tab again varies depending on the browser you're using. Similarly, if you save or cancel your edit, focus will disappear again as you move back to the non-edit view.

To give users a better experience, we'll add code to control the focus so that it gets set to the edit field when the edit form is shown. We'll also want to put focus back on the "Edit" button when a user cancels or saves their edit. In order to set focus, we need to understand a little bit more about how Vue works internally.

Virtual DOM and refs

Vue, like some other frameworks, uses a virtual DOM (VDOM) to manage elements. This means that Vue keeps a representation of all of the nodes in our app in memory. Any updates are first performed on the in-memory nodes, and then all the changes that need to be made to the actual nodes on the page are synced in a batch.

Since reading and writing actual DOM nodes is often more expensive than virtual nodes, this can result in better performance. However, it also means you often should not edit your HTML elements directly through native browser APIs (like Document.getElementById ) when using frameworks, because it results in the VDOM and real DOM going out of sync.

Instead, if you need to access the underlying DOM nodes (like when setting focus), you can use Vue refs . For custom Vue components, you can also use refs to directly access the internal structure of a child component, however this should be done with caution as it can make code harder to reason about and understand.

To use a ref in a component, you add a ref attribute to the element that you want to access, with a string identifier for the value of the attribute. It's important to note that a ref needs to be unique within a component. No two elements rendered at the same time should have the same ref.

Adding a ref to our app

So, let's attach a ref to our "Edit" Button in ToDoItem.vue . Update it like this:

<button type="button" class="btn" ref="editButton" @click="toggleToItemEditForm">
  编辑
  <span class="visually-hidden">{{label}}</span>
</button>

								

To access the value associated with our ref, we use the $refs property provided on our component instance. To see the value of the ref when we click our "Edit" button, add a console.log() to our toggleToItemEditForm() method, like so:

toggleToItemEditForm() {
  console.log(this.$refs.editButton);
  this.isEditing = true;
}

								

If you activate the "Edit" Button at this point, you should see an HTML <button> element referenced in your console.

Vue's $nextTick() 方法

We want to set focus on the "Edit" Button when a user saves or cancels their edit. To do that, we need to handle focus in the ToDoItem component’s itemEdited() and editCancelled() 方法。

For convenience, create a new method which takes no arguments called focusOnEditButton() . Inside it, assign your ref to a variable, and then call the focus() method on the ref.

focusOnEditButton() {
  const editButtonRef = this.$refs.editButton;
  editButtonRef.focus();
}

								

Next, add a call to this.focusOnEditButton() at the end of the itemEdited() and editCancelled() 方法:

itemEdited(newItemName) {
  this.$emit("item-edited", newItemName);
  this.isEditing = false;
  this.focusOnEditButton();
},
editCancelled() {
  this.isEditing = false;
  this.focusOnEditButton();
},

								

Try editing and then saving/cancelling a to-do item via your keyboard. You'll notice that focus isn’t being set, so we still have a problem to solve. If you open your console, you'll see an error raised along the lines of "can't access property "focus", editButtonRef is undefined" . This seems weird. Your button ref was defined when you activated the "Edit" Button, but now it’s not. What is going on?

Well, remember that when we change isEditing to true , we no longer render the section of the component featuring the "Edit" Button. This means there's no element to bind the ref to, so it becomes undefined .

You might now be thinking "hey, don’t we set isEditing=false before we try to access the ref , so therefore shouldn't the v-if now be displaying the button?” This is where the virtual DOM comes into play. Because Vue is trying to optimize and batch changes, it won't immediately update the DOM when we set isEditing to false . So when we call focusOnEdit() , the "Edit" Button has not been rendered yet.

Instead, we need to wait until after Vue undergoes the next DOM update cycle. To do that, Vue components have a special method called $nextTick() . This method accepts a callback function, which then executes after the DOM updates.

由于 focusOnEditButton() method needs to be invoked after the DOM has updated, we can wrap the existing function body inside a $nextTick() 调用。

focusOnEditButton() {
  this.$nextTick(() => {
    const editButtonRef = this.$refs.editButton;
    editButtonRef.focus();
  });
}

								

Now when you activate the "Edit" Button and then cancel or save your changes via the keyboard, focus should be returned to the "Edit" Button. Success!

Vue lifecycle methods

Next, we need to move focus to the edit form’s <input> element when the "Edit" button is clicked. However, because our edit form is in a different component to our "Edit" button, we can't just set focus inside the "Edit" button’s click event handler. Instead, we can use the fact that we remove and re-mount our ToDoItemEditForm component whenever the "Edit" Button is clicked to handle this.

So how does this work? Well, Vue components undergo a series of events, known as a lifecycle . This lifecycle spans from all the way before elements are created and added to the VDOM ( mounted ), until they are removed from the VDOM ( destroyed ).

Vue lets you run methods at various stages of this lifecycle using lifecycle methods . This can be useful for things like data fetching, where you may need to get your data before your component renders, or after a property changes. The list of lifecycle methods are below, in the order that they fire.

  1. beforeCreate() — Runs before the instance of your component is created. Data and events are not yet available.
  2. created() — Runs after your component is initialized but before the component is added to the VDOM. This is often where data fetching occurs.
  3. beforeMount() — Runs after your template is compiled, but before your component is rendered to the actual DOM.
  4. mounted() — Runs after your component is mounted to the DOM. Can access refs 这里。
  5. beforeUpdate() — Runs whenever data in your component changes, but before the changes are rendered to the DOM.
  6. updated() — Runs whenever data in your component has changed and after the changes are rendered to the DOM.
  7. beforeDestroy() — Runs before a component is removed from the DOM.
  8. destroyed() — Runs after a component has been removed from the DOM
  9. activated() — Only used in components wrapped in a special keep-alive tag. Runs after the  component is activated.
  10. deactivated() — only used in components wrapped in a special keep-alive tag.  Runs after the component is deactivated.

注意: The Vue Docs provide a nice diagram for visualizing when these hooks happen . This article from the Digital Ocean Community Blog dives into the lifecycle methods more deeply .

Now that we’ve gone over the lifecycle methods, let's use one to trigger focus when our ToDoItemEditForm component is mounted.

在 ToDoItemEditForm.vue , attach ref="labelInput" 到 <input> element, like so:

<input :id="id" ref="labelInput" type="text" autocomplete="off" v-model.lazy.trim="newName" />

								

Next, add a mounted() property just inside your component object — note that this should not be put inside the 方法 property, but rather at the same hierarchy level as props , data() ,和 方法 . Lifecycle methods are special methods that sit on their own, not alongside the user-defined methods. This should take no inputs. Note that you cannot use an arrow function here since we need access to this to access our labelInput ref.

mounted() {
}

								

在 mounted() method, assign your labelInput ref to a variable, and then call the focus() function of the ref. You don't have to use $nextTick here because the component has already been added to the DOM when mounted() 被调用。

mounted() {
   const labelInputRef = this.$refs.labelInput;
   labelInputRef.focus();
}

								

Now when you activate the "Edit" Button with your keyboard, focus should immediately be moved to the edit <input> .

Handling focus when deleting to-do items

There's one more place we need to consider focus management: when a user deletes a to-do. When clicking the "Edit" Button, it makes sense to move focus to the edit name text box, and back to the "Edit" button when canceling or saving from the edit screen.

However, unlike with the edit form, we don’t have a clear location for focus to move to when an element is deleted. We also need a way to provide assistive technology users with information that confirms that an element was deleted.

We're already tracking the number of elements in our list heading — the <h2> in App.vue — and it's associated with our list of to-do items. This makes it a reasonable place to move focus to when we delete a node.

First, we need to add a ref to our list heading. We also need to add a tabindex="-1" to it — this makes the element programmatically focusable (i.e. it can be focused via JavaScript), when by default it is not.

Inside App.vue , update your <h2> 如下:

 <h2 id="list-summary" ref="listSummary" tabindex="-1">{{listSummary}}</h2>

								

注意: tabindex is a really powerful tool for handling certain accessibility problems. However, it should be used with caution. Over-using tabindex="-1" can cause problems for all sorts of users, so only use it exactly where you need to. You should also almost never use tabindex > = 0 , as it can cause problems for users since it can make the DOM flow and the tab-order mismatch, and/or add non-interactive elements to the tab order. This can be confusing to users, especially those using screen readers and other assistive technology.

Now that we have a ref and have let browsers know that we can programmatically focus the <h2> , we need to set focus on it. At the end of deleteToDo() ,使用 listSummary ref to set focus on the <h2> . Since the <h2> is always rendered in the app, you do not need to worry about using $nextTick of lifecycle methods to handle focusing it.

deleteToDo(toDoId) {
    const itemIndex = this.ToDoItems.findIndex(item => item.id === toDoId);
    this.ToDoItems.splice(itemIndex, 1);
    this.$refs.listSummary.focus();
}

								

Now, when you delete an item from your list, focus should be moved up to the list heading. This should provide a reasonable focus experience for all of our users.

摘要

So that's it for focus management, and for our app! Congratulations for working your way through all our Vue tutorials. In the next article we'll round things off with some further resources to take your Vue learning further.

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

  • 上一
  • 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 21, 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