Vue drill down component

Posted by arimakidd on Tue, 11 Jan 2022 04:58:18 +0100

reference material

Component registration

Component name

When registering a component, you need to give it a name. For example, when registering globally, we have seen:

const app=Vue.createApp({...})

app.component('my-component-name',{
/****/
})

The component name is app The first parameter of the component.

The component name you define may depend on what you intend it to do. When using a component directly in the DOM (rather than in a string template or a single file component), it is highly recommended to follow W3C specification Custom component name in.

  1. All lowercase
  2. Include hyphens (and: multiple words are connected with hyphens)

This will help us avoid conflicts with current and future HTML elements.

You can Style guide Other suggestions on component names can be found in.

Component name case

When defining a component in a string template or a single file component, there are two ways to define the component name:

Using kebab case

app.component('my-component-name', {
  /* ... */
})

When defining a component in this way, you must also use kebab base when referencing the custom element, such as < My component name >

Using PascalCase

app.component('MyComponentName', {
  /* ... */
})

When a component is defined using this naming method, both naming methods can be used when referencing the custom element. In other words, < My component name > and < mycomponentname > are acceptable. Note, however, that only kebab case is valid when used directly in DOM (that is, non string templates).

Global registration

So far, we only use app Component to create a component:

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

These components are registered globally. That is, they can be used in the template of any newly created component instance after registration. For example:

const app = Vue.createApp({})

app.component('component-a', {
  /* ... */
})
app.component('component-b', {
  /* ... */
})
app.component('component-c', {
  /* ... */
})

app.mount('#app')
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

The same is true in all sub components, that is, the three components can also be used with each other in their own interior.

Local registration

Global registration is often not ideal. For example, if you use a build system like webpack, global registration of all components means that even if you don't use one of the components, it will still be included in the final build result.

In these cases, you can define the component through a common JavaScript object

const ComponentA = {
  /* ... */
}
const ComponentB = {
  /* ... */
}
const ComponentC = {
  /* ... */
}

Then define the components you want to use in the components option:

const app=Vue.createApp({
	components:{
		'component-a':ComponentA,
		'component-b':ComponentB
	}
})

For each property in the components object, its property name is the name of the custom element, and its property value is the option object of each component.

Note that locally registered components are not available in their subcomponents. For example, if you want ComponentA to be available in ComponentB, you need to write:

const ComponentA={
/*...*/
}

const ComponentB={
	components:{
		'component-a':ComponentA
	}
}

Or if you use the ES2015 module through Babel and webpack, the code looks like this:

import ComponentA from './ComponentA.vue'

export default{
	components:{
		ComponentA
	}
}

Note that in ES2015 +, putting a variable name similar to ComponentA in the object is actually the abbreviation of ComponentA:ComponentA, that is, the variable name is:

  • The name of the custom element used in the template
  • The variable name that contains this component option

Modular system

Local registration in module system

If you use modular systems such as Babel and webpack. In these cases, we recommend creating a component directory and placing each component in its own file.

Then you need to import each component you want to use before local registration. For example, in a hypothetical componentb JS or componentb In Vue file:

import ComponentA from './ComponentA'
import ComponentC from './ComponentC'

export default{
	components:{
		ComponentA,
		ComponentC
	}
}

Now both ComponentA and ComponentC can be used in the template of ComponentB

Props

Prop type

Here, we only see prop s listed in string array:

props:['title','likes','isPublished','commentIDs','author']

However, usually you want each prop to have a specified value type. Here, you can list props in the form of objects. The names and values of these properties are the respective names and types of props:

props:{
	title:String,
	likes:Number,
	isPublished:Boolean,
	commentIds:Array,
  	author: Object,
  	callback: Function,
  	contactsPromise: Promise // Or any other constructor
}

This not only provides documentation for your components, it will prompt users from the browser's JavaScript console when they encounter the wrong type.

Pass static or dynamic Prop

In this way, you already know that you can pass a static value to prop like this:

<blog-post title="My journey with Vue"></blog-post>

You also know that prop can be assigned dynamically through v-bind or short: dynamic assignment

In fact, any type of value can be passed to a prop

Pass in a number

<!-- even if `42` Is static, we still need `v-bind` To tell Vue     -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post :likes="42"></blog-post>

<!-- Dynamic assignment with a variable.-->
<blog-post :likes="post.likes"></blog-post>

Pass in a Boolean value

<!-- Include this prop If there is no value, it means `true`.           -->
<blog-post is-published></blog-post>

<!-- even if `false` Is static, we still need `v-bind` To tell Vue  -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post :is-published="false"></blog-post>

<!-- Dynamic assignment with a variable.                                -->
<blog-post :is-published="post.isPublished"></blog-post>

Pass in an array

<!-- Even if the array is static, we still need `v-bind` To tell Vue        -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post :comment-ids="[234, 266, 273]"></blog-post>

<!-- Dynamic assignment with a variable.                                -->
<blog-post :comment-ids="post.commentIds"></blog-post>

Pass in an object

<!-- Even if the object is static, we still need `v-bind` To tell Vue        -->
<!-- This is a JavaScript Expression instead of a string.             -->
<blog-post
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- Dynamic assignment with a variable.                                 -->
<blog-post :author="post.author"></blog-post>

Pass in all properties of an object

If you want to pass in all the properties of an object as prop, you can use v-bind without parameters. For example, for a given object, post:

post:{
	id:1,
	title:'My Journey with Vue'
}

The following template:

<blog-post v-bind="post"></blog-post>

Equivalent to:

<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>

Unidirectional data flow

All props form a one-way downstream binding between their parent and child props: the updates of the parent props will flow down to the child components, but not vice versa.

In addition, every time the parent component changes, all props in the child component will be refreshed to the latest value. This means that you should not change the prop inside a subcomponent. If you do, Vue issues a warning in the browser's console.

There are two common cases of trying to change a prop:

  1. This prop is used to pass an initial value; This subcomponent next wants to use it as a local prop data. In this case, it is better to define a local data property and take this prop as its initial value:
props:['initialCounter'],
data(){
	return{
		counter:this.initialCounter
	}
}
  1. This prop is passed in as an original value and needs to be converted. In this case, it is best to use the value of this prop to define a calculated attribute
props:['size'],
computed:{
	normalizedSize(){
		return this.size.trim().toLowerCase()
	}
}

Note that in JS, objects and arrays are passed in by reference, so for an array or object type prop, changing the object or array itself in the child component will affect the state of the parent component.

Prop verification

We can specify validation requirements for the prop of the component, such as these types you know. If a requirement is not met, Vue will warn you in the browser console.

In order to customize the verification method of prop, you can provide an object with verification requirements for the value in props instead of a string array. For example:

app.component('my-component', {
  props: {
    // Basic type check (` null 'and ` undefined' will pass any type verification)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true
    },
    // Number with default value
    propD: {
      type: Number,
      default: 100
    },
    // Objects with default values
    propE: {
      type: Object,
      // Object or array defaults must be obtained from a factory function
      default() {
        return { message: 'hello' }
      }
    },
    // Custom validation function
    propF: {
      validator(value) {
        // This value must match one of the following strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // Functions with default values
    propG: {
      type: Function,
      // Unlike the default value of an object or array, this is not a factory function -- it is a function used as the default value
      default() {
        return 'Default function'
      }
    }
  }
})

When the verification fails, Vue will generate a console warning.
Note that those props will be verified before a component instance is created, so the property of the instance (such as data, computed, etc.) is not available in the default or validator function.

Type check

type can be one of the following native constructors:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
    In addition, type can also be a custom constructor and checked and confirmed by instanceof. For example, give the following out of the box constructors:
function Person(firstName,lastName)
{
	this.firstName=firstName
	this.lastName=lastName
}

You can use:

app.component('blog-post',{
	props:{
		author:Person
	}
})

You can use:

app.component('blog-post', {
  props: {
    author: Person
  }
})

Used to verify whether the value of authorprop was created through new Person.

Case naming of Prop (camelCase vs kebab case)

attribute names in HTML are case insensitive, so browsers interpret all uppercase characters as lowercase characters. This means that when using the template in DOM, the prop name of camelCase needs to be named with equivalent kebab case (DASH delimited naming):

const app=Vue.createApp({})

app.component('blog-post', {
  // camelCase in JavaScript
  props: ['postTitle'],
  template: '<h3>{{ postTitle }}</h3>'
})
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>

If you use string templates, this restriction does not exist.

Non Prop Attribute

A non prop attribute refers to an attribute passed to a component, but the component does not have an attribute defined by the corresponding props or emits.
Common examples include class, style, and id attributes.

Attribute inheritance

When a component returns a single root node, a non prop attribute is automatically added to the root node's attribute. For example, in the instance of the < date picker > component:

app.component('date-picker', {
  template: `
    <div class="date-picker">
      <input type="datetime-local" />
    </div>
  `
})

If we need to define the state of the < date picker > component through data status property, it will be applied to the root node (div.date picker).

<!-- With non prop attribute of Date-picker assembly-->
<date-picker data-status="activated"></date-picker>

<!-- Render date-picker assembly -->
<div class="date-picker" data-status="activated">
  <input type="datetime-local" />
</div>

The same rules apply to event listeners:

<date-picker @change="submitChange"></date-picker>
app.component('date-picker', {
  created() {
    console.log(this.$attrs) // { onChange: () => {}  }
  }
})

This may be helpful when there is an HTML element with a change event that will be the root element of the date picker.

app.component('date-picker', {
  template: `
    <select>
      <option value="1">Yesterday</option>
      <option value="2">Today</option>
      <option value="3">Tomorrow</option>
    </select>
  `
})

In this case, the change event listener is passed from the parent component to the child component, which will be triggered on the change event of the native select. We don't need to issue events from date picker explicitly:

<div id="date-picker" class="demo">
  <date-picker @change="showChange"></date-picker>
</div>
const app = Vue.createApp({
  methods: {
    showChange(event) {
      console.log(event.target.value) // The value of the selected option is recorded
    }
  }
})

Disable Attribute inheritance

If you don't want the root element of the component to inherit attribute, you can set inheritattributes: false in the options of the component. For example, a common case of disabling attribute inheritance is to apply attribute to elements other than the root node.

By setting the inheritAttrs option to false, you can access the $attrsproperty of the component, which includes all properties never included in the component props and emitsproperty (for example, class style v-on listener, etc.).

If you need to apply all non prop attributes to non root elements, you can use the v-bind abbreviation (note the case of all non prop attributes).

app.component('date-picker', {
  inheritAttrs: false,
  template: `
    <div class="date-picker">
      <input type="datetime-local" v-bind="$attrs" />
    </div>
  `
})

With this new configuration, the data statusattribute will be applied to the input element

<!-- Date-picker Components using non prop attribute -->
<date-picker data-status="activated"></date-picker>

<!-- Render date-picker assembly -->
<div class="date-picker">
  <input type="datetime-local" data-status="activated" />
</div>

Attribute inheritance on multiple root nodes

Unlike a single root node component, components with multiple root nodes do not have automatic attribute fallback behavior. If $attrs is not explicitly bound, a runtime warning is issued.

Custom event

Event name

Like components and prop s, event names provide automatic case conversion. If a hump named character set triggers an event, you can add a kebab case listener to the parent component.

this.$emit('myEvent')
<my-component @my-event="doSomething"></my-component>

Like props, when you use DOM templates, we recommend using kebab case event listeners. If you use a string template, this restriction does not apply.

Define custom events

Emitted events can be defined on a component through the emits option.

app.component('custom-form', {
  emits: ['inFocus', 'submit']
})

When a native event (such as click) is defined in the emits option, the event in the component will be used instead of the native event listener.
TIP recommends defining all emitted events to better document how the component should work.

Verify events thrown

Similar to prop type validation, you can validate an emitted event if it is defined using object syntax instead of array syntax.

To add validation, the event is assigned a function that receives the parameters passed to the $emit call and returns a Boolean value indicating whether the event is valid.

app.component('custom-form',{
	emits:{
		click:null,
		
		submit:({emial,password})=>{
			if(emial&&password)
			{
				return true
			}
			else{
				return false
			}
		}
	},
	methods:{
		submitForm(email,password)
		{
			this.$emit('submit',{email,password})
		}
	}
})

v-model parameters

By default, v-models on components use modelValue as prop and update:modelValue as events. We can modify these names by passing parameters to v-model:

<my-component v-model:title="bookTitle"></my-component>

In this example, the sub component needs a titleprop and issues the update:title event to be synchronized:

app.component('my-component',{
props:{
title:String
},
emits:['update:title'],
template:`
<input type="text" :value="title" @input="$emit('update:title',$event.target.value)">
`
})

Multiple v-model bindings

By leveraging the ability to target specific prop s and events, you can create multiple v-model bindings on a single component instance.

Each v-model will be synchronized to a different prop without adding additional options to the component:

<user-name 
	v-model:first-name="firstName" 
	v-model:last-name="lastName"
></user-name>
app.component('user-name',{
props:{
	firstName:String,
	lastName:String
},
emits:['update:firstName','update:lastName'],
template:`
<input
type="text"
:value="firstName"
@input="$emit('update:firstName',$event.target.value)>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName',$event.target.value)>
`
}

Handling v-model modifiers

In 2 In X, we analyze the components on V - model Modifiers such as trim provide hard coding support. However, it is more useful if the component can support custom modifiers. In 3 In X, the modifiers added to the component v-model will be provided to the component through modelmodifiers prop:

Enter the binding content module in the previous form to know that the v-model has Built in modifier . However, in some cases, you may also need to add your own custom modifiers.

For example, create a custom modifier capitalize to capitalize the first letter of the string provided by the v-model binding.

Modifiers added to the component v-model are provided to the component through modelmodifiers prop.

Note that when the created lifecycle hook on the component is triggered, modelModifiersprop will contain capitalize and its value is true, because capitalize is set to v-model Capitalize = "mytext" is bound to the v-model.

<my-component v-model.capitalize="myText"></my-component>
app.component('my-component',{
		props:{
			modelValue:String,
			modelModifiers:{
				default:()=>({})
			}
		},
		emits:['update:modelValue'],
		template:`
			<input type="text"
			:value="modelValue"
			@input="$emit('update:modelValue',$event.target.value)"
			>
			`
		created(){
		 console.log(this.modelModifiers) // { capitalize: true }
		}	
	}
)

Now that prop is set, we can check the modelModifiers object key and write a processor to change the issued value. In the following code, we capitalize the string whenever the < input / > element triggers an input event.

<div id="app">
	<my-component v-model.capitalize="myText"></my-component>
	{{myText}}
</div>
const app=Vue.createApp({
	data(){
		return{
			myText:''
		}
	}
})

app.component('my-component',{
	props:{
		modelValue:String,
		modelModifiers:{
			default:()=>({})
		}
	},
emits:['update:modelValue'],
methods:{
	emitValue(e){
	let value=e.target.value
	if(this.modelModifiers.capitalize){
		value=value.charAt(0).toUpperCase() + value.slice(1)
	}
	this.$emit('update:modelValue',value)
	}
},
template:`
<input type="text"
:value="modelValue"
@input="emitValue"
>
`
})

app.mount('#app')

For v-model binding with parameters, the generated prop name will be arg+"modifiers":

<my-component v-model:description.capitalize="myText"></my-component>
app.component('my-component',{
	props:['description','descriptionModifiers'],
	emits:['update:description'],
	template:`
	<input type="text"
	:value="description"
	@input="$emit('update:description',$event.target.value)"
	>
	`,
	created() {
	    console.log(this.descriptionModifiers) // { capitalize: true }
	}
})

slot

Slot content

Vue implements a set of content distribution API s, using the < slot > element as an exit to host the distribution content.
It allows you to compose components like this:

<todo-button>
 add todo
</todo-button>

Then in the template of < todo button >, you may have

<button class="btn-primary">
	<slot></slot>
</button>

When the component is rendered, < slot > < / slot > will be replaced with "Add Todo".

<button class="btn-primary">
 add todo
</button>

However, the string is just the beginning! The slot can also contain any template code, including HTML:

<toto-button>
 <i class="fas fa-plus"></i>
 Add todo
</todo-button>

Or other components

<todo-button>
 <font-awesome-icon name="plus"></font-awesome-icon>
 Add todo
</todo-button>

If the < todo button > template does not contain a < slot > element, any content between the start tag and the end tag of the component will be discarded.

<!-- todo-button Component template -->

<button class="btn-primary">
  Create a new item
</button>
<todo-button>
  <!-- The following text is not rendered -->
  Add todo
</todo-button>

Render scope

When you want to use data in a slot, for example:

<todo-button>
 Delete a {{item.name}}
</todo-button>

This slot can access the same instance property (i.e. the same "scope") as the rest of the template.

The slot cannot access the scope of < todo button >. For example, attempting to access an action will not work:

<todo-button action="delete">
	Clicking here will {{action}} an item
	<!-- `action` Not defined because its content is passed*reach* <todo-button>,instead of*stay* <todo-button>Defined in.  -->
</todo-button>

As a rule, remember:
All contents in the parent template are compiled in the parent scope; Everything in the child template is compiled in the child scope.

Alternate content

Sometimes it is useful to set specific alternate (i.e. default) content for a slot, which will only be rendered when no content is provided. For example, in a < submit button > component:

<button type="submit">
 <slot></slot>
</button>

We may want to render the text "Submit" in most cases in this < button >. In order to use "Submit" as an alternative, we can put it in the < slot > tab:

<button type="submit">
 <slot>submit</slot>
</button>

Named slot

Sometimes we need multiple slots. For example, for a < base layout > component with the following template:

<div class="container">
	<header>
	</header>
	<main>
	</main>
	<footer>
	</footer>
</div>

In this case, the < slot > element has a special attribute: name. This attribute can be used to define additional slots:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

A < slot > exit without name will have the implied name "default".

When providing content to a named slot, we can use the v-slot instruction on a < template > element and provide its name in the form of v-slot parameters:

<base-layout>

	<template v-slot:header>
		<h1>Here might be a page title</h1>
	</template>
	
	<template v-slot:default>
		<p>A paragraph for the main content.</p>
		<p>And another one.</p>
	</template>
	
	<template v-slot:footer>
		<p>Here's some contact info</p>
	</template>
	
</base-layout>

Now everything in the < template > element will be passed into the corresponding slot.

Scope slot

Sometimes it is useful to make the slot content have access to data that is only available in the subcomponent. This is a common situation when a component is used to render an array of items. We want to be able to customize the rendering method of each item.

For example, we have a component that contains a list of todo items.

app.component('todo-list',{
	data(){
		return{
			items:['feed a cat','buy milk']
		}
	},
	template:`
	<ul>
		<li v-for="(item,index) in items">
			{{item}}
		</li>
	</ul>
	`
})

We may want to replace {{item}} with < slot > to customize on the parent component.

<todo-list>
 <i class="fas fa-check"></i>
 <span class="green>{{item}}</span>
</todo-list>

But this will not work, because only the < todo list > component can access the item, and we will provide the slot content from its parent component.

To make the item available to the slot content provided by the parent, we can add a < slot > element and bind it as an attribute:

<ul>
	<li v-for="(item,index) in items">
		<slot :item="item">
		</slot>
	</li>
</ul>

You can bind many attribute s to slot s according to your needs.

<ul>
	<li v-for="(item,index) in items">
		<slot :item="item" :index="index" :another-attribute="anotherAttribute">
		</slot>
	</li>
</ul>

The attribute bound to the < slot > element is called slot prop. Now in the parent scope, we can use the v-slot with value to define the name of the slot prop we provide:

<todo-list>
	<template v-slot:default="slotProps">
		<i class="fas fa-check"></i>
		<span class="green">{{slotProps.item}}</span>
	</template>
</todo-list>

Abbreviation syntax for exclusive default slots

In the above case, when the content provided is only the default slot, the label of the component can be used as the template of the slot. In this way, you can use v-slot directly on components:

<todo-list v-slot:default="slotProps">
		<i class="fas fa-check"></i>
		<span class="green">{{slotProps.item}}</span>
</todo-list>

This can be simpler. Just as the unspecified content is assumed to correspond to the default slot, the v-slot without parameters is assumed to correspond to the default slot:

<todo-list v-slot="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
</todo-list>

Note that the abbreviation syntax of the default slot cannot be mixed with the named slot because it will lead to ambiguous scope:

<!-- Invalid, causing a warning -->
<todo-list v-slot="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
  
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</todo-list>

Whenever multiple slots appear, always use the full based syntax for all slots:

Deconstruct slot Prop

The internal working principle of a scoped slot is to include the slot contents in a function that passes in a single parameter

Dynamic slot name

Abbreviation for named slot

Like the root v-on and v-bind, v-slot also has abbreviations, that is, replace all the contents before the parameter (v-slot:) with #:

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <template #default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

However, like other instructions, this abbreviation is only available when it has parameters. This means that the following syntax is invalid:

<!-- This will trigger a warning -->

<todo-list #="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

If you want to use abbreviations, you must always replace them with explicit slot names:

<todo-list #default="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

Provide/Inject

Dynamic component & asynchronous component

Template reference

Although there are prop s and events, sometimes you may still need direct access to subcomponents. Therefore, you can use refattribute to specify reference ID s for child components or HTML elements. For example:

<input ref="input">

For example, you want to programmatically focus on this input when the component is mounted, which may be useful

const app=Vue.createApp({})

app.component('base-input',{
	template:`
	<input ref="input">
	`,
	methods:{
		focusInput(){
			this.$refs.input.focus()
		}
	},
	mounted()
	{
		this.focusInput()
	}
})

In addition, you can add another ref to the component itself and use it to trigger the focusInput event from the parent component:

<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput.focusInput()

Note that $refs will only take effect after the component is rendered. This is only an alternative way to directly manipulate child elements, and access to $refs in templates or calculated attributes should be avoided.

Handling boundary conditions

Topics: Front-end Vue