assembly
-
Components can extend HTML elements to encapsulate reusable code
-
At a higher level, components are custom elements for which the Vue.js compiler adds special functionality
-
In some cases, components can also be in the form of native HTML elements, extending with is features.
Using components
Register a global component
<div id="example"> <! - The definition of web components is different from the general description of dom elements, which is equivalent to customizing elements - >. <my-component></my-component> </div>
// Register the global component, specify the previously set element name, and then pass in the object
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// Create a root instance
new Vue({
el: '#example'
})
Locally Registered Components
There is no need to register each component globally. By registering component instance options, components can be made available only in the scope of another instance/component
//Write the object passed to the component separately
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
//Creating Local components through components Syntax
//Place components only in this vue instance for use
components: {
// < my-component > will only be available in the parent template
'my-component': Child
}
})
DOM template parsing instructions
When using DOM as a template (for example, mounting the el option on an existing element), you will be limited by HTML.
Because Vue can only get template content after the browser parses and standardizes HTML. Especially, elements like <ul>, <ol>, <table>, <select> restrict elements that can be wrapped by them, and <option> can only appear inside other elements.
<! - This is not possible, and will make mistakes - >. <table> <my-row>...</my-row> </table> <! - to be processed through the is attribute - > <table> <tr is="my-row"></tr> </table>
data must be a function
When using components, most of the options that can be passed into the Vue constructor can be used when registering components, with one exception: data must be a function. Actually
//This will result in an error, prompting data to be a function
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data is a function, so Vue does not warn.
// But we returned the same object reference for each component, so changing one would change the other.
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})
Avoid changing data at the same time
//Return a new object instead of the same data object reference
data: function () {
return { //Literals create new objects
counter: 0
}
}
Components
Components mean collaborative work, and usually the parent-child relationship is as follows:
-
Component A uses component B in its template. They must communicate with each other.
-
The parent component passes data to the child component, which needs to inform the parent of what happens inside it.
However, it is important to decouple parent and child components as much as possible in a well-defined interface. This ensures that each component can be written and understood in a relatively isolated environment, and greatly improves the maintainability and reusability of components.
In Vue.js, the relationship between parent and child components can be summarized as props down, events up.
The parent component passes data down to the child component through props, and the child component sends messages to the parent component through events. See how they work.
prop
Using prop to transfer data
-
The scope of component instances is isolated. This means that data from the parent component cannot and should not be directly referenced in the template of the child component.
-
Use props to pass data to subcomponents.
-
prop is a custom property used by parent components to pass data
-
Subcomponents need to explicitly declare "prop" with the props option
<div id="example-2"> <! - Pass in a string to the component - > <child message="hello!"></child> </div>
Vue.component('child', {
// Declare props, objects in array form
props: ['message'],
// Like data, prop can be used in templates
// It can also be used in vm instances like "this.message"
template: '<span>{{ message }}</span>'
});
new Vue({
el: '#example-2'
})
Dynamic prop
The value of props is dynamically bound to the data of the parent component with v-bind. Whenever the data of the parent component changes, the change is also transmitted to the child component.
<div id="example-2"> <! - Using v-modal to achieve bi-directional binding - > <input v-model="parentMsg"> <br> <! - Note the use of short-crossed variables here, because short-crossed variables are used under html, but hump variables are used under vue - > <! - Bind parent Msg of parent component to my-message of child component - > 2. <child v-bind:my-message="parentMsg"></child> </div>
Vue.component('child', {
// Declare props
props: ['my-message'],
template: '<span>{{ myMessage }}</span>' //If writing my-message will cause an error, it needs to be converted to hump writing
});
new Vue({
el: '#example-2',
data: {
parentMsg: ''
}
})
Short horizon and hump writing
HTML features are case-insensitive. When using a non-string template, the name form of prop changes from camelCase to kebab-case (separated by short horizontal lines).
-
stay JavaScript It uses hump writing, but in html it needs to be converted to short horizontal writing.
-
Conversely, vue automatically handles short horizontal lines from html to hump lines
Literary Quantity Grammar and Dynamic Grammar
<! -- By defau lt, only one string "1" --> is passed. <comp some-prop="1"></comp> <! - Transfer the actual number with v-bind <comp v-bind:some-prop="1"></comp>
Unidirectional data stream
-
prop is unidirectionally bound
-
When the attributes of the parent component change, they are passed to the child component, but not vice versa. This is to prevent the child component from unintentionally modifying the state of the parent component -- which makes the data flow of the application difficult to understand.
-
Every time the parent component is updated, all props of the child component are updated to the latest value. This means that you shouldn't change prop within subcomponents. If you do, Vue will give a warning in the console.
There are usually two situations in which prop is changed:
-
prop is passed in as an initial value, and the subcomponent then simply uses its initial value as the initial value of the local data.
Define a local data attribute and take the initial value of prop as the initial value of local data.
<div id="example-2"> <! - Here it's written in short horizontal lines - >. <child initial-counter="10"></child> </div>
Vue.component('child', {
props: ['initialCounter'],//Here it's written in hump style.
data: function () { //Convert to a local variable and write a data object for component use
return {counter: this.initialCounter}
},
template: '<span>{{ counter }}</span>'
});
new Vue({
el: '#example-2'
})
-
prop is passed in as the original value that needs to be transformed.
Define a computed attribute that is calculated from the value of prop.
//The example is not finished, but according to the first example, we can see that the principle of using computer is almost the same as writing a data.
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Note that objects and arrays are reference types in JavaScript, pointing to the same memory space. If prop is an object or array, changing it inside a child component affects the state of the parent component.
prop verification
Components can specify validation requirements for props, which is useful when components are used by others.
Vue.component('example', {
props: {
// Basic type detection (`null'means any type can be)
propA: Number,
// Various types
propB: [String, Number],
// Necessary and String
propC: {
type: String,
required: true
},
// Numbers, with default values
propD: {
type: Number,
default: 100
},
// The default value of an array/object should be returned by a factory function
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// Custom Validation Function
propF: {
validator: function (value) {
return value > 10
}
}
}
})
Custom Events
Each Vue instance implements an Events interface
-
Use $on(eventName) to listen for events
-
Use $emit(eventName) to trigger events
-
The parent component can use v-on directly to listen for events triggered by the child component where the child component is used.
<div id="counter-event-example"> <p>{{ total }}</p> <! -- Listen for event triggers of subcomponents, increment1 events, and incrementTotal events as handlers - > <button-counter v-on:increment1="incrementTotal"></button-counter> The key here is that v-on binds an event of a child component and assigns him a method of a parent component, so that this method can be used in the child component - >. <button-counter v-on:increment1="incrementTotal"></button-counter> </div>
Vue.component('button-counter', {
//Listen for click events, and the handler is increment (a method defined by a subcomponent)
template: '<button v-on:click="increment">{{ counter }}</button>',
//Each counter is a separate object attribute
data: function () {
return {
counter: 0
}
},
//Subcomponent approach
methods: {
increment: function () {
this.counter += 1;
//The increment1 event listened for before triggering directly in the child component to execute the method of the parent component
this.$emit('increment1');
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
//Method of parent component
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
1. Components are independent of each other because of their different scopes, so a new listening mapping is needed if the child component wants to use the parent component's method.
Binding native events to components
<! - Instead of. on, you can bind native js events - > <my-component v-on:click.native="doTheThing"></my-component>
Form Input Components Using Custom Events
Custom events can also be used to create custom form input components, using v-model for data bidirectional binding. So to make the component's v-model work, it must:
-
Accept a value attribute
-
Trigger the input event when there is a new value
<! - Processing input events by defau lt using V-model directly <input v-model="something"> <!-v-modal is grammatical sugar, and the translation principle is as follows: --> <! -- Bind a value, then listen to the input event, change the value of the binding by getting input, satisfy the trigger condition of v-modal, and then realize v-modal - -->. <input v-bind:value="something" v-on:input="something = $event.target.value">
A very simple money input:
<! - Binding a v-model to price actually binds a value - > to price. <currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\ //Registered as input, is the node element of DOM
v-bind:value="value"\ //Value of v-model (also prop)
v-on:input="updateValue($event.target.value)"\ //Functions encapsulating update value s
>\
</span>\
',
props: ['value'], //The parent component passes the bound value to the child component
methods: {
// Instead of updating values directly, this method is used to format and limit the number of digits of input values.
updateValue: function (value) {
var formattedValue = value //Processing values
// Delete space characters on both sides
.trim()
// Keep 2 decimal and 2 digits
.slice(0, value.indexOf('.') + 3)
// If the values are not uniform, manually overwrite to maintain consistency, in order to keep the display content of the input box consistent with the formatted content
if (formattedValue !== value) {
//Because registration is an input element, this.$refs is an input element.
this.$refs.input.value = formattedValue
}
//Manually trigger the input event and pass the formatted value to it, which is the output of the final display input box.
this.$emit('input', Number(formattedValue))
}
}
})
//Instantiating vue instance
new Vue({
el: '#aa', //To bind a vue instance, for example, wrap a div with id aa
data:{
price:'' //v-model needs data source
}
})
ref is used to register reference information for elements or subcomponents. Reference information is registered according to the $refs object of the parent component. If used on common DOM elements, reference information is the element; if used on subcomponents, reference information is the component instance. ref
This is a relatively complete example:
<div id="app">
<!--There are three components,Different v-model-->
<currency-input
label="Price"
v-model="price"
></currency-input>
<currency-input
label="Shipping"
v-model="shipping"
></currency-input>
<currency-input
label="Handling"
v-model="handling"
></currency-input>
<currency-input
label="Discount"
v-model="discount"
></currency-input>
<p>Total: ${{ total }}</p>
</div>
Vue.component('currency-input', {
template: '\
<div>\
<label v-if="label">{{ label }}</label>\
$\
<input\
ref="input"\ // Nothing special about these. References are registered as input DOM elements
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
v-on:focus="selectAll"\ //There are more focus event monitoring, the focus is selected when the focus is on, but only more processing. It has no effect on the overall logical understanding.
v-on:blur="formatValue"\ //There are more blur event listeners, formatting when the focus is off
>\
</div>\
',
props: { //Multiple prop transitions, because prop is an object, as long as it is in object format
value: {
type: Number,
default: 0
},
label: {
type: String,
default: ''
}
},
mounted: function () { //This is the transitional state of vue, temporary neglect does not affect understanding
this.formatValue()
},
methods: {
updateValue: function (value) {
var result = currencyValidator.parse(value, this.value)
if (result.warning) {
// It also uses $refs to get reference registration information.
this.$refs.input.value = result.value
}
this.$emit('input', result.value)
},
formatValue: function () {
this.$refs.input.value = currencyValidator.format(this.value) //Notice here that this is prop passed in, which corresponds to the scope of the component.
},
selectAll: function (event) { //event can retrieve native js events
// Workaround for Safari bug
// http://stackoverflow.com/questions/1269722/selecting-text-on-focus-using-jquery-not-working-in-safari-and-chrome
setTimeout(function () {
event.target.select()
}, 0)
}
}
})
new Vue({
el: '#app',
data: {
price: 0,
shipping: 0,
handling: 0,
discount: 0
},
computed: {
total: function () {
return ((
this.price * 100 +
this.shipping * 100 +
this.handling * 100 -
this.discount * 100
) / 100).toFixed(2)
}
}
})
Non-parent-child component communication
In a simple scenario, an empty Vue instance is used as the central event bus:
var bus = new Vue()
// Triggering events in component A
bus.$emit('id-selected', 1)
/*
on is used to listen for events of subcomponents to achieve delivery
*/
// Listen for events in hooks created by component B
bus.$on('id-selected', function (id) {
// ...
})
Distributing content using Slot
To make components composable, we need a way to mix the content of the parent component with the template of the child component itself. This process is called content distribution (or "transclusion" if you are familiar with Angular)
Compile scope
Component scope simply means that the content of the parent component template is compiled within the scope of the parent component, and the content of the child component template is compiled within the scope of the child component. Assuming some ChildProperty is an attribute of a subcomponent, the above example will not work as expected. The parent component template should not know the state of the child component.
<!-- invalid -->
<child-component v-show="someChildProperty"></child-component>
If you want to bind instructions in a subcomponent to the root node of a component, you should do so in its template:
Vue.component('child-component', {
// Effective, because it's in the right scope
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return { //Because this property is compiled (created) within the current component
someChildProperty: true
}
}
})
Similarly, the distribution content is compiled within the scope of the parent component.
Single Slot
-
Unless the child component template contains at least one < slot > socket, the content of the parent component will be discarded.
-
When the child component template has only one slot without attributes, the entire content fragment of the parent component will be inserted into the DOM location where the slot is located, and the slot tag itself will be replaced.
-
The standby content is compiled within the scope of the child component and displayed only when the host element is empty and there is no content to insert.
Parent Component Template: --> <div id="aa"> <h1> I am the title of the parent component</h1> <! -- Compiled within the scope of the subcomponent, the host element is empty, and there is no content to insert - >. <my-component></my-component> <my-component> <p> These are some of the initial elements.</p> <p> This is more initial content.</p> </my-component> </div>
Vue.component('my-component', { // my-component component has the following template template: '\ <div>\ <h2> I am the title of the subcomponent</h2>\ <slot>// slot socket, so it is not discarded by the parent component Only when there is no content to distribute will it be displayed. " </slot> \ </div> \ ' }) new Vue({ el: '#aa', })
Rendering results:
<div id="aa">h1>I am the title of the parent component</h1> <div> <h2> I am the title of the subcomponent</h2> <! - This is a direct insert, without using DOM elements - >. Only when there is no content to distribute will it be displayed. </div> <div> <h2> I am the title of the subcomponent</h2> <p> These are some of the initial elements.</p> <p> This is more initial content.</p> </div> </div>
Named lot
-
The < slot > element can configure how to distribute content with a special attribute name. Multiple slots can have different names. Named slots match elements with slot characteristics in content fragments.
-
You can still have an anonymous slot, which is the default slot, as an alternate slot for content fragments that cannot be matched. If there is no default slot, the content fragments that cannot be matched will be discarded.
<div id="aa"> <app-layout> <! - This is header - >. <h1 slot= "header"> Here may be a page title</h1> <p> A paragraph of the main content. </p> <p> Another main paragraph. </p> <! - This is footer - >. <p slot="footer">Here are some contact information</p> </app-layout> </div>
Vue.component('app-layout', {
template: '\
<div class="container"> \
<header> \ //Replace the content after finding the slot named header, where the entire DOM is replaced
<slot name="header"></slot> \
</header> \
<main> \ //Because slot has no attributes, the content is inserted into the DOM location where the slot is located.
<slot></slot> \
</main> \
<footer>\ //Similar to header
<slot name="footer"></slot> \
</footer> \
</div> \
'
});
new Vue({
el: '#aa',
})
The rendering results are as follows:
<div class="container"> <header> <h1> Here may be a page title.</h1> </header> <main> <p> A paragraph of the main content. </p> <p> Another main paragraph. </p> </main> <footer> <p> Here are some contact information</p> </footer> </div>
Scope slot (vue2.1)
-
Scope slots are special types of slots used to replace rendered elements with a reusable template that can transfer data to them.
-
In subcomponents, you just need to pass data to the slot, just as you pass prop to the component.
-
In the parent level, the < template > element with a special attribute scope indicates that it is a template for scope slots. The value of scope corresponds to a temporary variable name that receives the prop object passed from the subcomponent
<div id="parent" class="parent"> <child> <! -- Receives the prop object passed from the subcomponent (this is the scope slot)--> <template scope="props"> <span>hello from parent</span> <! -- Use this prop object - >. <span>{{ props.text }}</span> </template> </child> </div>
Vue.component('child', {
props: ['props'], //This can be written or not, the scope slot is fixed to receive the prop object, and the prop object is sure to exist.
template: '\
<div class="child"> \
<slot text="hello from child"></slot> \ //Pass data directly to slot in subcomponents
</div> \
'
});
new Vue({
el: '#parent',
})
Rendering results:
<div class="parent">
<div class="child">
<span>hello from parent</span>
<!--The sub-components are here.-->
<span>hello from child</span>
</div>
</div>
Another example, a more representative use case for scoped slots is list components, which allow components to customize how each item of the list should be rendered.
<div id="parent"> <! - Bind a component's prop, location 1 - >. <my-awesome-list :items="items"> <! - Scope slots can also be named here - >. <! - Here props only means to determine what accepts the prop object, not what's inside the prop object, location 2 - >. <template slot="item" scope="props"> <li class="my-fancy-item">{{ props.text }}</li> </template> </my-awesome-list> </div>
Vue.component('my-awesome-list', {
props:['items'], //You need to declare prop as items, and you need to set the data source for items traversed in the following loop, location 3
template: '\
<ul> \
<slot name="item" v-for="item in items" :text="item.text"> \ //In slot, loop through the text of the output items at position 4
</slot> \
</ul> \
'
});
new Vue({
el: '#parent',
data : {
items:[ //Initialize items data
{text:"aa"},
{text:"bb"}
]
}
})
-
Location 1 implements a component's prop binding. Pro needs to be declared in the component. Here, items are bound. This is to pass items from the parent component to the child component. So it needs to be declared in Location 3 and initialized in the vue instance.
-
Location 2, where the scope slot receives data from the prop object, and the props.text is the text attribute of the prop object that represents the output of each li.
-
Location 3, declare props in the component to receive the items attribute bound by the parent component, and then recycle it to Location 4
-
Location 4, where the text attribute is bound, is the text attribute of the prop object output in the front call location 2.
Dynamic components
Multiple components can use the same mount point and then switch between them dynamically. Use the retained < component > element to dynamically bind to its is feature
var vm = new Vue({
el: '#example',
data: {
currentView: 'home' //Default values
},
components: { //Components are used to switch components according to different values.
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
<! -- This is a string that gives components v-bind - > based on the return value. <component v-bind:is="currentView"> <! - Components change when vm. current view changes! > </component>
keep-alive
If the switched component is kept in memory, its state can be preserved or re-rendering can be avoided. To do this, you can add a keep-alive instruction parameter
<keep-alive> <component :is="currentView"> <! - Inactive components will be cached! > </component> </keep-alive>
miscellaneous
Writing reusable components
When writing components, remember whether it's good to reuse components. One-off components are tightly coupled with other components, but reusable components should define a clear open interface. The API of the Vue component comes from three parts - props, events and slots:
-
Props allows external environments to pass data to components
-
Events allow components to trigger side effects of external environments
-
Slots allows external environments to combine additional content into components.
<!--v-bind,Abbreviation:,binding prop-->
<!--v-on,Abbreviation@,Monitoring events-->
<!--slot slot-->
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat"
>
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>
Subcomponent Index
Despite props and events, it is sometimes necessary to access subcomponents directly in JavaScript. For this purpose, ref can be used to specify an index ID for subcomponents.
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// Access subcomponents
var child = parent.$refs.profile
-
When ref and v-for are used together, ref is an array or object that contains the corresponding subcomponents.
-
Refs is populated only after component rendering is complete, and it is non-responsive. It is only an emergency solution for direct access to subcomponents -- you should avoid using $refs in templates or computational properties.
-
ref is used to register reference information for elements or subcomponents. Reference information is registered according to the $refs object of the parent component. If used on common DOM elements, reference information is the element; if used on subcomponents, reference information is the component instance. ref
Component naming conventions
-
When registering components (or props), kebab-case, camelCase, or TitleCase can be used. Vue doesn't care about that.
-
In HTML templates, use kebab-case format:
// In component definition
components: {
// Register in kebab-case form--horizontal line writing
'kebab-cased-component': { /* ... */ },
// register using camelCase -- Hump writing
'camelCasedComponent': { /* ... */ },
// register using TitleCase -- Title writing
'TitleCasedComponent': { /* ... */ }
}
<! -- kebab-case -- horizontal line writing - > is always used in HTML templates. <kebab-cased-component></kebab-cased-component> <camel-cased-component></camel-cased-component> <title-cased-component></title-cased-component>
Recursive components
-
Components can recursively call themselves in their templates, but only if they have the name option
-
When you register a component globally using Vue.component, the global ID is set automatically as the component's name option.
//Components can use name to write names
name: 'unique-name-of-my-component'
//You can also add names by default when creating
Vue.component('unique-name-of-my-component', {
// ...
})
//If used at the same time, recursion will continue to recurse itself, leading to overflow.
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
Cheap-Static-Component: Low-level Static Component Using-v-once-
Although rendering HTML in Vue is fast, when there is a lot of static content in the component, you can consider using v-once to cache the rendering results, just like this:
Vue.component('terms-of-service', {
template: '\
<div v-once>\
<h1>Terms of Service</h1>\
... a lot of static content ...\
</div>\
'
})
v-once renders elements and components only once. Subsequently re-rendered, elements/components and all their child nodes will be treated as static content and skipped. This can be used to optimize update performance.