Handle edge cases


This page assumes that you have read Component Basics. If you don’t know much about components yet, I recommend you read it first.

What is recorded here are functions related to handling edge cases, that is, some special cases that require some small adjustments to Vue rules. However, please note that these functions all have disadvantages or dangerous scenarios. We will note this in each case, so please pay attention when you use each function.


Directory

  • ##Access Elements & Components

    • Access to the root instance

    • Access to the parent component instance

    • Access child component instances or child elements

    • Dependency injection

  • Programmatic event listener

  • Circular reference

    • Recursive components

    • Circular references between components

  • Alternatives to template definitions

    • Inline templates

    • X -Template

  • ##Control update

    • Force update

    • Create low-overhead static components through v-once


Accessing Elements & Components

In most cases, it is best not to touch the inside or outside of another component instance Manually manipulate DOM elements. But there are certainly situations where doing these things is appropriate.



Access the root instance in each

new Vue

Among the child components of an instance, its root instance can be accessed through the $root attribute. For example, in this root instance:

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})
All child components can access or use this instance as a global store.

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

This is convenient for demos or very small applications with a small number of components. However, this model does not extend to medium and large applications. Therefore, in most cases, we strongly recommend using Vuex to manage the state of the application.


## Access parent component instance

and

$root Similarly, the $parent property can be used to access an instance of a parent component from a child component. It provides an opportunity to reach the parent component at any time later, instead of passing data into the child component in the form of props.

In most cases, reaching the parent component will make your application more difficult to debug and understand, especially when you change the parent component's data. When we look back at that component later, it's hard to figure out where that change originated.

In addition, when it may be appropriate, you need to specifically share some component libraries. For example, within an abstract component that interacts with the JavaScript API without rendering HTML, such as these hypothetical Google Maps components:

<google-map>
  <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>

This

<google-map> component You can define a map attribute that all child components need to access. In this case <google-map-markers> you might want to access that map via something like this.$parent.getMap in order to add a set of markers to it. You can check out this pattern here.

Please note that, despite this, the internals of the component built through this pattern are still prone to problems. For example, imagine we add a new

<google-map-region> component. When <google-map-markers> appears inside it, it will only render Markers within that area:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

Then inside

<google-map-markers> you may find yourself needing some hack like this:

var map = this.$parent.map || this.$parent.$parent.map

Soon It will get out of control. This is why we recommend

Dependency Injection when you need to provide contextual information to any deeper components.


Accessing child component instances or child elements

Despite the existence of props and events, sometimes you You may still need to access a child component directly in JavaScript. To achieve this, you can assign an ID reference to the child component via the

ref attribute. For example:

<base-input ref="usernameInput"></base-input>

Now in the component where you have defined this

ref, you can use:

this.$refs.usernameInput

to access this <base-input> instance for emergencies. For example, programmatically focus the input box from a parent component. In the example just now, the <base-input> component can also use a similar ref to provide access to the specified element inside, for example:

<input ref="input">

You can even define methods through its parent component:

methods: {
  // 用来从父级组件聚焦输入框
  focus: function () {
    this.$refs.input.focus()
  }
}

This allows the parent component to focus on the input box in <base-input> via the following code:

this.$refs.usernameInput.focus()

When ref is used with v-for, the reference you get will be an array containing these subcomponents of the corresponding data source.

$refs will only take effect after the component is rendered, and they are not responsive. This only serves as an "escape hatch" for directly manipulating child components - you should avoid accessing $refs in templates or computed properties.


Dependency Injection

Before that, before we describe Accessing the parent When level component instance, an example similar to this was shown:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

In this component, all descendants of <google-map> need to access a getMap method to know which map to interact with. Unfortunately, using the $parent attribute does not scale well to deeper nested components. This is where dependency injection comes in, with two new instance options: provide and inject. The

provide option allows us to specify the data/methods we want to provide to descendant components. In this example, it is the getMap method inside <google-map>:

provide: function () {
  return {
    getMap: this.getMap
  }
}

Then in any descendant component, we can use inject option to receive the specified attributes we want to add on this instance:

inject: ['getMap']

You can see the complete example here. Compared with $parent, this usage allows us to access getMap in any descendant component without exposing the entire <google-map> instance. . This allows us to better continue developing the component without worrying that we might change/remove something that subcomponents depend on. At the same time, the interfaces between these components are always clearly defined, just like props.

In fact, you can think of dependency injection as part of the "widely valid prop", except that:

  • The ancestor component does not need to know which descendant components use it to provide Properties

  • Descendant components do not need to know where the injected properties come from

However, dependency injection still has negative effects. It couples the components in your application to the way they are currently organized, making refactoring more difficult. Also the properties provided are non-responsive. This is by design, because using them to create a centralized scale of data and using $root is not good enough. If the property you want to share is specific to your application rather than generic, or if you want to update the provided data in an ancestor component, then this means you may need to switch to a Vuex like Such a real state management solution.

You can learn more about dependency injection in the

API reference documentation.


Programmatic event listeners


Now, you know it# The usage of ##$emit

can be listened by v-on, but the Vue instance also provides other methods in its event interface. We can:

    Pass
  • $on(eventName, eventHandler)

    Listen for an event

  • Pass
  • $ once(eventName, eventHandler)

    Listen to an event at one time

  • Stop listening to an event through
  • $off(eventName, eventHandler)

    ##You won't normally use these, but they come in handy when you need to manually listen for events on a component instance. They can also be used in code organization tools. For example, you may often see this pattern of integrating a third-party library:

    // 一次性将这个日期选择器附加到一个输入框上
    // 它会被挂载到 DOM 上。
    mounted: function () {
      // Pikaday 是一个第三方日期选择器的库
      this.picker = new Pikaday({
        field: this.$refs.input,
        format: 'YYYY-MM-DD'
      })
    },
    // 在组件被销毁之前,
    // 也销毁这个日期选择器。
    beforeDestroy: function () {
      this.picker.destroy()
    }
  • There are two potential problems here:

It requires an instance of the component Save this

picker
    . If possible, it is best that only life cycle hooks can access it. This is not a serious problem, but it can be considered clutter.
  • Our build code is independent of our cleanup code, which makes it harder to programmatically clean up everything we build.

  • You should solve these two problems through a programmatic listener:

    mounted: function () {
      var picker = new Pikaday({
        field: this.$refs.input,
        format: 'YYYY-MM-DD'
      })
      this.$once('hook:beforeDestroy', function () {
        picker.destroy()
      })
    }
  • Using this strategy, I can even have multiple input box elements at the same time With a different Pikaday, each new instance programmatically cleans itself up later:
mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })
    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

Check out

this fiddle

for the complete code. Note that even so, if you find yourself having to do a lot of setup and cleanup work in a single component, it's usually best to create more modular components. In this example, we recommend creating a reusable

<input-datepicker>

component.

To learn more about programmatic listeners, please check out the Instance Methods/Events related APIs.

Note that Vue’s event system is different from the browser’s EventTarget API. Although they work similarly, $emit, $on, and $off are not dispatchEvent, addEventListener Aliases for and removeEventListener.


Circular Reference


Recursive Component

Components can call themselves in their own templates. However, they can only do this through the name option:

name: 'unique-name-of-my-component'

When you use Vue.component to register a component globally, the global ID will automatically Set to the name option for this component.

Vue.component('unique-name-of-my-component', {
  // ...
})

If you are not careful, recursive components may cause infinite loops:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

Components similar to the above will cause "max stack size exceeded" errors, so please ensure that the recursive call is conditional (e.g. using a v-if which will end up with false).


Circular references between components

Suppose you need to build a file directory tree, like accessing Like Dart or Explorer. You might have a <tree-folder> component, with a template like this:

<p>
  <span>{{ folder.name }}</span>
  <tree-folder-contents :children="folder.children"/>
</p>

And a <tree-folder-contents> component, with a template It goes like this:

<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>

When you look closely, you'll find that these components are each other's descendants and ancestors in the rendering tree - a paradox! This paradox is automatically resolved when the component is globally registered via Vue.component. If this is what you do, then you can skip this.

However, if you use a module system to depend on/import components, such as through webpack or Browserify, you will encounter an error:

Failed to mount component: template or render function not defined.

To explain what is going on here, let’s first put the two The components are called A and B. The module system discovers that it needs A, but first A depends on B, but B depends on A, but A depends on B, and so on. This becomes a loop and I don't know how to completely parse out one of the components without going through the other. To solve this problem, we need to give the module system a point where "A needs B anyway, but we don't need to parse B first."

In our example, set the <tree-folder> component to that point. We know that the child component that causes the paradox is the <tree-folder-contents> component, so we will wait until the life cycle hook beforeCreate to register it:

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

Or, when registering components locally, you can use webpack's asynchronous import:

components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

This problem is solved!


Alternatives to template definitions


Inline templates

When inline-template this special feature appears on a child component, the component will use the content inside it as a template, and Not as content to be distributed. This makes template writing more flexible.

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

Inline templates need to be defined within the DOM element to which Vue belongs.

However, inline-template will make the scope of the template more difficult to understand. So as a best practice, please select the template option first within the component or a <template> element in the .vue file to define the template.


X-Template

Another way to define a template is in a <script> element, and give it the type of text/x-template, and then reference the template through an id. For example:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

x-template needs to be defined outside the DOM element to which Vue belongs.

These can be used in demos or very small applications where the template is particularly large, but otherwise please avoid using it as it will separate the template from other definitions of the component.


Control updates


Thanks to Vue’s responsive system, it always Know when to update (if you do it right). However, there are some edge cases where you want to force an update even though the reactive data appears to have not changed. There are also situations where you want to prevent unnecessary updates.


Force update

If you find yourself needing to update in Vue When doing a forced update, 99.9% of the time, you did something wrong somewhere.

You may not have noticed the change detection considerations for arrays or objects, or you may be relying on one that is not tracked by Vue's reactive system status.

However, if you have done the above and still find that in rare cases you need to force an update manually, then you can do this via $forceUpdate.


Create low-overhead static components via v-once

Rendering Ordinary HTML elements are very fast in Vue, but sometimes you may have a component that contains a lot of static content. In this case, you can add the v-once attribute on the root element to ensure that the content is only calculated once and cached, like this:

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

One more time , try not to overuse this pattern. On the rare occasions when you need to render a lot of static content, it will bring you convenience, but unless you are very aware of the rendering slowdown, it is completely unnecessary - plus it will cause a lot of trouble in post. Puzzled. For example, imagine that another developer is not familiar with v-once or misses it in the template. They may spend many hours trying to figure out why the template does not update correctly.