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
- ##Control update
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:
All child components can access or use this instance as a global store. // Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
// 获取根组件的数据 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.
<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.mapSoon 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 theref 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
You can learn more about dependency injection in theHowever, 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.
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 fiddlefor 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, Recursive Component Components can call themselves in their own templates. However, they can only do this through the When you use If you are not careful, recursive components may cause infinite loops: 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 Circular references between components Suppose you need to build a file directory tree, like accessing Like Dart or Explorer. You might have a And a 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 However, if you use a module system to depend on/import components, such as through webpack or Browserify, you will encounter an error: 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 Or, when registering components locally, you can use webpack's asynchronous This problem is solved! Inline templates When Inline templates need to be defined within the DOM element to which Vue belongs. However, X-Template Another way to define a template is in a 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. 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 Create low-overhead static components via 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 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 $emit
, $on
, and $off
are not dispatchEvent
, addEventListener Aliases for
and removeEventListener
. Circular Reference
name
option: name: 'unique-name-of-my-component'
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', {
// ...
})
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
v-if
which will end up with false
). <tree-folder>
component, with a template like this: <p>
<span>{{ folder.name }}</span>
<tree-folder-contents :children="folder.children"/>
</p>
<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>
Vue.component
. If this is what you do, then you can skip this. Failed to mount component: template or render function not defined.
<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
}
import
: components: {
TreeFolderContents: () => import('./tree-folder-contents.vue')
}
Alternatives to template definitions
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-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. <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'
})
Control updates
$forceUpdate
. v-once
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>
`
})
v-once
or misses it in the template. They may spend many hours trying to figure out why the template does not update correctly.