What are components?
Component is one of the most powerful features of Vue.js. Components can extend HTML elements and encapsulate reusable code. At a higher level, components are custom elements, and Vue.js' compiler adds special features to it. In some cases, components can also be in the form of native HTML elements, extended with the is feature.
Usage Components
register
As mentioned before, we can create a component constructor using Vue.extend():
var MyComponent = Vue.extend({ // Options...})To use this constructor as a component, you need to register with `Vue.component(tag, constructor)` **:
// Globally register the component, the tag is my-componentVue.component('my-component', MyComponent)<p>Vue.js does not mandate the W3C rule (lowercase, and includes a short bar) for custom tag names, although it is better to follow this rule.
After the component is registered, it can be used as a custom element <my-component> in the module of the parent instance. To make sure the component is registered before initializing the root instance:
<div id="example"> <my-component></my-component></div>// Define var MyComponent = Vue.extend({ template: '<div>A custom component!</div>'})// Register Vue.component('my-component', MyComponent)// Create root instance new Vue({ el: '#example'})Rendered as:
<div id="example"> <div>A custom component!</div></div>
Note that the template of the component replaces the custom element, and the function of the custom element is only as a mount point. You can use the instance option replace to decide whether to replace it.
Local registration
There is no need to register each component globally. It can only be used in other components, register with the instance option components:
var Child = Vue.extend({ /* ... */ })var Parent = Vue.extend({ template: '...', components: { // <my-component> can only be used in parent component template 'my-component': Child }})This encapsulation is also suitable for other resources such as instructions, filters, and transitions.
Register syntactic sugar
To make the event simpler, you can pass in the option object directly instead of the constructor to the Vue.component() and component options. Vue.js automatically calls Vue.extend() behind the back:
// Extend and register Vue.component('my-component', { template: '<div>A custom component!</div>'})// This is also possible with local registration var Parent = Vue.extend({ components: { 'my-component': { template: '<div>A custom component!</div>' } }})Component Options Issues
Most options passed into the Vue constructor can also be used in Vue.extend(), but there are two special cases: data and el. Imagine if we simply pass an object as a data option to Vue.extend():
var data = { a: 1 }var MyComponent = Vue.extend({ data: data})The problem with this is that all instances of MyComponent will share the same `data` object! This is basically not what we want, so we should use a function as the `data` option to return a new object:
var MyComponent = Vue.extend({ data: function () { return { a: 1 } }})Similarly, the `el` option must also be a function when used in `Vue.extend()`.
Template analysis
Vue's template is a DOM template, which uses a browser native parser instead of implementing one by itself. Compared to string templates, DOM templates have some benefits, but there are problems, and it must be a valid HTML snippet. Some HTML elements have limitations on what elements can be placed inside it. Common limitations:
•a cannot contain other interactive elements (such as buttons, links)
•ul and ol can only directly include li
•select can only include option and optgroup
•table can only directly include head, tbody, tfoot, tr, caption, col, colgroup
•tr can only include th and td directly
In practice, these limitations can lead to unexpected results. Although it may work in simple cases, you cannot rely on custom components to expand results before browser validation. For example, <my-select><option>...</option></my-select> is not a valid template, even if the my-select component ends up expanding to <select>...</select>.
Another result is that custom tags (including custom elements and special tags, such as <component>, <template>, <partial>) cannot be used in tags that have restrictions on internal elements, such as ul, select, table, etc. Custom tags placed inside these elements will be mentioned outside the element, thus rendering incorrectly.
For custom elements, the is attribute should be used:
<table> <tr is="my-component"></tr></table>
`` cannot be used in ``, `` at this time, ``
There can be multiple ``:
<table> <tbody v-for="item in items"> <tr>Even row</tr> <tr>Odd row</tr> </tbody></table>
Props
Passing data using Props
The scope of component instances is orphaned. This means that the parent component's data cannot and should not be referenced directly within the template of the child component. You can use props to pass data to child components.
"prop" is a field of component data that is expected to be passed down from the parent component. The child components need to explicitly declare props with the props option:
Vue.component('child', { // Declare props props: ['msg'], // prop can be used in templates// You can use `this.msg` to set template: '<span>{{ msg }}</span>'})Then pass it a normal string:
<child msg="hello!"></child>
Hump vs. horizontal bar
HTML attributes are case-insensitive. When a prop with the name form camelCase is used as a feature, it needs to be converted to kebab-case (short horizontal lines separated):
Vue.component('child', { // camelCase in JavaScript props: ['myMessage'], template: '<span>{{ myMessage }}</span>'})<!-- kebab-case in HTML --><child my-message="hello!"></child>Dynamic Props
Similar to using v-bind to bind HTML attributes to an expression, you can also use v-bind to bind dynamic Props to the data of the parent component. Whenever the data of the parent component changes, it is also transmitted to the child component:
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child></div>
Using the abbreviation syntax of `v-bind` is usually simpler:
<child :my-message="parentMsg"></child>
Literal syntax vs. Dynamic syntax
A common mistake for beginners is to pass numeric values using literal syntax:
<!-- Passed a string "1" -->
<comp some-prop="1"></comp>
Because it is a literal prop, its value is passed in the string `"1"` instead of the actual number. If you want to pass an actual JavaScript number, you need to use dynamic syntax so that its value is calculated as a JavaScript expression:
<!-- Pass the actual number -->
<comp :some-prop="1"></comp>
Prop Binding Type
prop is one-way binding by default: when the properties of the parent component change, it will be passed to the child component, but the other way around will not. This is to prevent the child component from accidentally modifying the state of the parent component - this will make the application's data flow difficult to understand. However, it is also possible to explicitly force bidirectional or single-time binding using the .sync or .once binding modifier:
Comparative syntax:
<!-- Default is one-way binding--><child :msg="parentMsg"></child><!-- Bidirectional binding--><child :msg.sync="parentMsg"></child><!-- Single binding--><child :msg.once="parentMsg"></child>
Bidirectional binding synchronizes the msg attribute of the child component back to the parentMsg attribute of the parent component. Single binding will not synchronize the changes after creation.
Note that if prop is an object or array, it is passed by reference. Modifying it within a child component will affect the state of the parent component, regardless of the binding type used.
Prop Verification
Components can specify verification requirements for props. This is useful when components are given to others, because these verification requirements form the component's API, ensuring that others use the component correctly. At this time, the value of props is an object that contains verification requirements:
Vue.component('example', { props: { // Basic type detection (`null` means any type is OK) propA: Number, // Multiple types (1.0.21+) propM: [String, Number], // Required and string propB: { type: String, required: true }, // Number, with default values propC: { type: Number, default: 100 }, // The default value of an object/array should be returned by a function to propD: { type: Object, default: function () { return { msg: 'hello' } } }, // Specify this prop as a two-way binding// If the binding type is not correct, a warning will be thrown propE: { twoWay: true }, // Custom verification function propF: { validator: function (value) { return value > 10 } }, // Convert the value (new in 1.0.12) // Convert the value before setting the value propG: { coerce: function (val) { return val + '' // Convert the value to a string} }, propH: { coerce: function (val) { return JSON.parse(val) // Convert the JSON string to an object} } }})type can be the following native constructor:
•String
•Number
•Boolean
•Function
•Object
•Array
type can also be a custom constructor that uses instanceof detection.
When prop verification fails, Vue refuses to set this value on the child component, and a warning will be thrown if the development version is used.
Parent-child component communication
Parent link
A child component can access its parent component with this.$parent. Descendants of the root instance can access it with this.$root. The parent component has an array this.$children that contains all its child elements.
Although any instance on the parent chain can be accessed, child components should avoid relying directly on the parent component's data and try to use props to pass data explicitly. Also, it is very bad to modify the state of the parent component in a child component because:
1. This allows the parent component to be closely coupled with the child component;
2. If you only look at the parent component, it is difficult to understand the state of the parent component. Because it may be modified by any child component! Ideally, only the component can modify its state itself.
Custom events
The Vue instance implements a custom event interface for communication in the component tree. This event system is independent of native DOM events and uses it differently.
Each Vue instance is an event trigger:
•Use $on() to listen for events;
• Use $emit() to trigger an event on it;
• Use $dispatch() to distribute events, and the events bubble along the parent chain;
• Use $broadcast() to broadcast events, and the events are transmitted downward to all descendants.
Unlike the DOM event, the Vue event automatically stops bubble after the first callback is triggered during the bubble process, unless the callback explicitly returns true.
Simple example:
<!-- Child Component Template--><template id="child-template"> <input v-model="msg"> <button v-on:click="notify">Dispatch Event</button></template><!-- Parent Component Template--><div id="events-example"> <p>Messages: {{ messages | json }}</p> <child></child></div>// Register child component// Send the current message out Vue.component('child', { template: '#child-template', data: function () { return { msg: 'hello' } }, methods: { notify: function () { if (this.msg.trim()) { this.$dispatch('child-msg', this.msg) this.msg = '' } } }})// Initialize the parent component // Push the event into an array when the message is received var parent = new Vue({ el: '#events-example', data: { messages: [] }, // When creating an instance, the `events` option simply calls `$on` events: { 'child-msg': function (msg) { // The `this` in the event callback is automatically bound to the instance where it is registered this.messages.push(msg) } }})Use v-on to bind custom events
The above example is very good, but we cannot intuitively see where the "child-msg" event comes from the code of the parent component. It would be better if we declare the event handler where subcomponents are used in templates. For this subcomponents, you can use v-on to listen for custom events:
<child v-on:child-msg="handleIt"></child>
This makes it clear: when the child component triggers the "child-msg" event, the parent component's `handleIt` method will be called. All code that affects the state of the parent component is placed in the parent component's `handleIt` method; the child component only focuses on the triggering event.
Subcomponent index
Despite props and events, sometimes it is still necessary to access the child components directly in JavaScript. For this purpose, you can use v-ref to specify an index ID for the child component. For example:
<div id="parent"> <user-profile v-ref:profile></user-profile></div>var parent = new Vue({ el: '#parent' })// Access child component var child = parent.$refs.profileWhen v-ref and v-for are used together, ref is an array or object that contains corresponding child components.
Use Slot to distribute content
When using components, you often have to combine them like this:
<app> <app-header></app-header> <app-footer></app-footer></app>
Note two points:
1. The <app> component does not know what content will be in its mount point. The content of the mount point is determined by the parent component of <app>.
2. The <app> component is likely to have its own template.
In order for components to be combined, we need a way to mix the contents of the parent component with the child component's own template. This processing is called content distribution (or "translation" if you are familiar with Angular). Vue.js implements a content distribution API that refers to the current web component specification draft and uses a special <slot> element as a slot for the original content.
Compile scope
Before diving into the content distribution API, we first clarify the scope of the content compilation. Assume that the template is:
<child-component>
{{ msg }}
</child-component>
Should msg be bound to the parent component's data, or data bound to the child component? The answer is the parent component. The component scope is simply:
The content of the parent component template is compiled within the scope of the parent component; the content of the child component template is compiled within the scope of the child component
A common mistake is trying to bind a directive to the properties/method of a child component within the parent component template:
<!-- Invalid-->
<child-component v-show="someChildProperty"></child-component>
Assuming someChildProperty is a property of a child component, the above example will not work as expected. The parent component template should not know the status of the child component.
If you want to bind instructions in a subcomponent to the root node of a component, you should do this in its template:
Vue.component('child-component', { // works because it is in the correct scope template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } }})Similarly, the distribution content is compiled within the scope of the parent component.
Single Slot
The contents of the parent component will be discarded unless the child component template contains <slot>. If the child component template has only one slot without characteristics, the entire content of the parent component will be inserted where the slot is and replace it.
The content of the <slot> tag is considered to be rollback content. The fallback content is compiled within the scope of the child component, and the fallback content is displayed when the host element is empty and there is no content for insertion.
Assume that the my-component component has the following template:
<div> <h1>This is my component!</h1> <slot> If there is no content distributed, it will show me. </slot></div>
Parent component template:
<my-component>
<p>This is some original content</p>
<p>This is some more original content</p>
</my-component>
Rendering result:
<div> <h1>This is my component!</h1> <p>This is some original content</p> <p>This is some more original content</p></div>
Named Slot
The <slot> element can be configured with a special feature name to configure how to distribute content. Multiple slots can have different names. A named slot will match elements in the content fragment that have the corresponding slot attribute.
There can still be an anonymous slot, which is the default slot, as a fallback slot for matching content fragments that cannot be found. Without a default slot, these unmatched content fragments will be discarded.
For example, suppose we have a multi-insertion component with a template like:
<div> <slot name="one"></slot> <slot></slot> <slot name="two"></slot></div>
Parent component template:
<multi-insertion> <p slot="one">One</p> <p slot="two">Two</p> <p>Default A</p></multi-insertion>
The rendering result is:
<div> <p slot="one">One</p> <p>Default A</p> <p slot="two">Two</p></div>
Content distribution API is a very useful mechanism when combining components.
Dynamic Components
Multiple components can use the same mount point and then dynamically switch between them. Use the reserved <component> element to dynamically bind to its is property:
new Vue({ el: 'body', data: { currentView: 'home' }, components: { home: { /* ... */ }, posts: { /* ... */ }, archive: { /* ... */ } }})<component :is="currentView"> <!-- Component changes when vm.currentview changes--></component>Keep-alive
If the switched out component is kept in memory, it can be retained or re-rendered. To do this, you can add a keep-alive directive parameter:
<component :is="currentView" keep-alive>
<!-- Inactive components will be cached -->
</component>
activate hook
When switching components, cutting into components may require some asynchronous operations before cutting into them. To control the duration of component switching, add an activate hook to the cutting component:
Vue.component('activate-example', { activate: function (done) { var self = this loadDataAsync(function (data) { self.someData = data done() }) }})Note that the Activate hook only acts during dynamic component switching or static component initialization rendering, and does not act in manual insertion using instance methods.
transition-mode
The transition-mode feature is used to specify how two dynamic components transition.
By default, enter and leave transition smoothly. This feature can specify two other modes:
•in-out: The new component first transitions into, and after its transition is completed, the current component will transition out.
•out-in: The current component first transitions out, and after its transition is completed, the new component transitions into the process.
Example:
<!-- Fade out first and then fade in --><component :is="view" transition="fade" transition-mode="out-in"></component>.fade-transition { transition: opacity .3s ease;}.fade-enter, .fade-leave { opacity: 0;}Miscellaneous
Components and v-for
Custom components can use v-for directly like normal elements:
<my-component v-for="item in items"></my-component>
However, data cannot be passed to a component because the scope of the component is orphaned. To pass data to components, props should be used:
<my-component
v-for="item in items"
:item="item"
:index="$index">
</my-component>
The reason why the item is not automatically injected into the component is that this causes the component to be tightly coupled to the current v-for. Explicitly declare where the data comes from and can be reused elsewhere for components.
Writing reusable components
When writing components, it is beneficial to remember whether to reuse components. There is nothing to do with the tight coupling of disposable components to other components, but reusable components should define a clear public interface.
The Vue.js component API comes from three parts: prop, events and slot:
•prop allows external environment to pass data to components;
• Events allow components to trigger actions in the external environment;
•slot allows the external environment to insert content into the component's view structure.
Using the abbreviation syntax of v-bind and v-on, the indentation of the template is clear and concise:
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat">
<!-- content -->
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>
Asynchronous Components
In large applications, we may need to split the application into small pieces and download it from the server only if needed. To make things easier, Vue.js allows the component to be defined as a factory function, dynamically parsing the definition of the component. Vue.js only triggers the factory function when the component needs to be rendered, and caches the result for later re-rendering. For example:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { resolve({ template: '<div>I am async!</div>' }) }, 1000)})The factory function receives a resolve callback, called when a component definition downloaded from the server is received. You can also call reject(reason) to indicate that loading has failed. Here setTimeout is just for demonstration. It is entirely up to you to get the components. Recommended code segmentation function with Webpack:
Vue.component('async-webpack-example', function (resolve) { // This special require syntax tells webpack // Automatically split the compiled code into different blocks, // These blocks will be automatically downloaded through ajax request. require(['./my-async-component'], resolve)})Resource naming convention
Some resources, such as components and directives, appear in templates as HTML attributes or HTML custom elements. Because the names of HTML attributes and tag names are not case-sensitive, the name of the resource usually needs to use the form of kebab-case instead of camelCase, which is not very convenient.
Vue.js supports the name of resources in the form of camelCase or PascalCase and automatically converts them into kebab-case in the template (similar to the naming convention of prop):
// In component definition components: { // Register myComponent with camelCase form: { /*... */ }}<!-- Using kebab-case form in templates--><my-component></my-component>ES6 object literal abbreviation is also fine: // PascalCaseimport TextBox from './components/text-box';import DropdownMenu from './components/dropdown-menu';export default { components: { // Write <text-box> and <dropdown-menu> TextBox, DropdownMenu }}Recursive components
Components can call themselves recursively within their templates, but only if it has the name option:
var StackOverflow = Vue.extend({ name: 'stack-overflow', template: '<div>' + // Call it recursively on its own'<stack-overflow></stack-overflow>' + '</div>'})The above component will cause an error "max stack size exceeded", so make sure that the recursive call has a termination condition. When a component is registered globally using Vue.component(), the component ID is automatically set to the component's name option.
Fragment instance
When using the template option, the contents of the template replace the instance's mount element. Therefore, the top-level elements of the recommended template are always single elements.
Don't write the template like this:
<div>root node 1</div>
<div>root node 2</div>
It is recommended to write this:
<div>
I have a single root node!
<div>node 1</div>
<div>node 2</div>
</div>
The following situations will turn the instance into a fragmented instance:
1. The template contains multiple top-level elements.
2. The template only contains normal text.
3. The template only contains other components (other components may be a fragment instance).
4. The template contains only one element directive, such as <partial> or <router-view> of vue-router.
5. The template root node has a process control instruction, such as v-if or v-for.
These cases allow the instance to have an unknown number of top-level elements, which will treat its DOM content as a fragment. The fragment instance still renders the content correctly. However, it does not have a root node, its $el points to an anchor node, that is, an empty text node (a comment node in development mode).
But more importantly, non-process control instructions, non-prop features and transitions on component elements will be ignored because there is no root element for binding:
<!-- No, because there is no root element -->
<example v-show="ok" transition="fade"></example>
<!-- props yes->
<example :prop="someData"></example>
<!-- Process control is OK, but there is no transition -->
<example v-if="ok"></example>
Of course, fragment instances have their uses, but it is usually better to give the component a root node. It ensures that the instructions and special performance on component elements are correctly converted, and the performance is also slightly better.
Inline templates
If a child component has the inline-template property, the component will treat its content as its template instead of treating it as its distribution content. This makes the template more flexible.
<my-component inline-template>
<p>These are compiled as the component's own template</p>
<p>Not parent's translation content.</p>
</my-component>
However, inline-template makes the scope of the template difficult to understand and cannot cache the template compilation results. A best practice is to use the template option to define templates within a component.
This article has been compiled into the "Vue.js Front-end Component Learning Tutorial", and everyone is welcome to learn and read.
For tutorials on vue.js components, please click on the special topic vue.js component learning tutorial to learn.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.