This is the third day of my participation in the August More Text Challenge


series

  • Vue 3 basis
  • Vue 3 dynamic effect
  • Vue 3 components
  • Modular API
  • Vue Router Next
  • Vuex 4

This article introduces the component-related basics of Vue 3, focusing on the differences between 🎉 and Vue 2.

The component of Vue is essentially an instance with predefined options, and we use small, independent, and often reusable components that are layered together to form a complete page.

Components must be registered so that the Vue application can recognize them. There are two types of component registration:

  • Global registration
  • Local registration

Global components

(in the root component) use the 🎉 method app.component.ponent (‘ component-name ‘, {}) to register global components that can be used in the template of any component in the application.

The first parameter is the component name, which is recommended to follow the W3C specification’s custom component name (to avoid conflicts with current and future HTML elements) : all lowercase and must contain a hyphen. The second parameter is the configuration option for the component.

const app = Vue.createApp();
app.component('my-component', {
    template: `

Hello World!

`
}); const vm = app.mount('#app') Copy the code

The ⚠️ global component can be easily used in a variety of components (including their own internals), but this can lead to larger build projects and unnecessary downloads of JavaScript by users.

💡 requires global component registration before app.mount(‘#app’) is mounted to the DOM

Local components

A component registered in the components option of a component in a (parent) component.

These child components are defined by a plain JavaScript object that takes the same parameters as the global component, but they can only be used within the parent component and are called local components.

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

const ComponentA = {
  / *... * /
}
const ComponentB = {
  / *... * /
}
const ComponentC = {
  / *... * /
}
Copy the code
// Then define the component you want to use in the parent component's 'Components' option
const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
Copy the code

Dynamic components

The built-in < Component :is=”componentName />” tag is used to dynamically express different components by controlling the value of the parameter bound to attribute IS.

The attribute is can be:

  • The name of the registered component
  • An option object for a component

💡 Sometimes dynamic components can be wrapped with the tag
in order to preserve the state of a dynamic component during a switch, such as the value of an input field in the component.

The 💡 attribute is can also be used to work around the rule limitations of HTML element nesting by applying it to a native HTML tag whose value is the component name, so that what the native tag actually renders is the component.

Because there is a strict limit to the number of direct child elements that can be placed inside

    , < OL >,

    , and < SELECT >, embedding other elements would be considered invalid content, and promotion to the outside would cause final rendering problems. But if we need to use components as direct child elements in these elements, we can use the attribute is on the “legal” child elements to specify the actual content to be rendered. In this case, the attribute is is used on native HTML elements, such as

    whose value 🎉 needs to be vue: The prefix indicates that what is being parsed is actually a Vue component
<table>
  <tr is="vue:blog-post-row"></tr>
</table>
Copy the code

However, the above limitations are only encountered when using Vue templates directly in HTML, not when using templates in the following steps:

  • String, for exampletemplate: '... '
  • Single file component.vue
  • <script type="text/x-template">

Asynchronous components

Large web pages today often need to fetch different data asynchronously. Vue has a defineAsyncComponent method that defines asynchronous components to optimize application loading and user experience.

Asynchronous components do not render the page immediately upon loading, but wait until some business logic is complete before executing the logic within the component and rendering it to the page.

// Global components
app.component('async-example', Vue.defineAsyncComponent(() = > {
  return new Promise((resolve, reject) = > {
    resolve({
      template: '
      
I am async!
'
}})})))// Local components import { createApp, defineAsyncComponent } from 'vue' createApp({  // ...  components: {    AsyncComponent: defineAsyncComponent(() = > {      return new Promise((resolve, reject) = > {        resolve({          template: '
I am async!
'
})})})})Copy the code

The registration of an asynchronous component is similar to that of a synchronous component in general. App.component () is used to register a global component, but the second argument tells the Vue application that the component is asynchronous using the vue.defineasyncComponent () method.

The argument to the defineAsyncComponent() method is an anonymous function that returns a Promise. Within the Promise, you should resovlve({}) an object that contains configuration parameters related to the build component. Processing of asynchronous components is performed only when Promise resolve or Reject.

Transfer values between components

props

Pass data to child components through Prop. Prop is a set of custom attributes registered on components. When a value is passed to a prop attribute, it becomes a property of that component instance.

Note the following points:

  • If the data passed in the parent component is non-string, it needs to be passed by v-bind:propName (even if static values such as pure numbers are passed); Otherwise, passing the data directly will be converted to a string.
  • Prop should follow one-way data flow, that is, prop values should not be modified in child components (because the data is in the parent layer)

It can be listed as an array of strings or as an object (you can specify the data type that prop receives, default values, whether it is required, validation conditions, and so on).

// ...
props: {
  // Basic type checking (' null 'and' undefined 'will pass any type verification)
  propA: Number.// Multiple configuration items can be set
  propB: {
    type: String.required: true.default: 'abc'.validator: func()   // A custom validation function that returns true/false
  }
  // Objects with default values
  propC: {
    type: Object.// Object or array defaults must be obtained from a factory function
    default() {
      return { message: 'hello'}}},// A function with default values
  propD: {
    type: Function.// Unlike the object or array defaults, this is not a factory function, but a function that is used as the default
    default() {
      return defaultFunction(){... }}}}Copy the code

The option type checks prop, supports native types and custom constructors (Vue checks prop with instanceof) :

  • String

  • Number

  • Boolean

  • Array

  • Object

  • Date

  • Function

  • Symbol

  • Custom constructors

    // Custom constructor
    function Person(firstName, lastName) {
      this.firstName = firstName
      this.lastName = lastName
    }
    
    app.component('blog-post', {
      props: {
        author: Person // Verify that the author value is created by new Person}})Copy the code

⚠️ These prop validations are performed before a component instance is created, so component instance properties such as data, computed, and so on are not available in the prop validation options default or validator functions.

💡 If you want all the attributes of an object to be passed in separately as prop, you can use V-bind with no arguments to achieve an effect similar to object deconstruction

post: {
  id: 1.title: 'My Journey with Vue'
}
Copy the code

The calling component

<blog-post v-bind="post"></blog-post>
Copy the code

equivalent

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

In addition to passing the underlying data types, 💡 can also pass functions as prop, passing functions defined at the parent level to child components for use.

// Register a global component
app.component('date-component', {props: ['getDate'].methods: {clickHandler(){
      this.getDate()  // Get the current time}},template:'
      
prints the current time
'
}) Copy the code
const app = Vue.createApp({
  data() {
    return {
      getDate: () = > {
        const today = new Date(a);console.log(today.toLocaleDateString())
      }
    }
  },
  template: ` 

Date

`
}) Copy the code

Dojo.provide and inject

In addition to passing values from parent components to child components through props, provide and inject can be used to pass values from an ancestor component to its descendants, no matter how deep the component hierarchy is nested.

The parent component has a provide option to provide data, and the child component has an Inject option to start using this data.

/ / the parent component
app.component('todo-list', {
  provide: {
    user: 'John Doe'}})/ / child component
app.component('todo-list-statistics', {
  inject: ['user'].created() {
    console.log(`Injected property: The ${this.user}`) // Inject property: John Doe}})Copy the code

⚠️ To access the parent component instance property, we need to convert provide to a function that returns an object

/ / the parent component
app.component('todo-list', {
  data() {
    return {
      todos: ['Feed a cat'.'Buy tickets']}},provide() {
    return {
      todoLength: this.todos.length // Access todos property}},template: `... `
})
Copy the code

💡 By default, provide/ Inject binding does not have responsiveness, and data can be implemented using combined API computed. You can also use ref Property or Reactive objects to wrap variables in setup and pass them to provide for responsiveness.

app.component('todo-list', {
  // ...
  provide() {
    return {
      todoLength: Vue.computed(() = > this.todos.length) // Implement responsive packaging with computed
    }
  }
})

app.component('todo-list-statistics', {
  inject: ['todoLength'].created() {
    console.log(`Injected property: The ${this.todoLength.value}`) // Injected property: 5}})Copy the code

emit

$emit(‘eventName’, value); $emit(‘eventName’, value); $emit(‘eventName’, value); Value generally refers to the data that needs to be changed.

The parent layer then listens for custom events thrown by this child component via V-ON :eventName or @EventName.

If the child component throws data along with the event (the second argument value), the value thrown can be accessed via $event in the parent’s inline event handler.

<blog-post . @enlarge-text="postFontSize += $event"></blog-post>
Copy the code

If the parent event handler is a method, the thrown data is passed to the method as the first argument

<blog-post . @enlarge-text="onEnlargeText"></blog-post>
Copy the code
methods: {
  onEnlargeText(enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}
Copy the code

💡 It is recommended to use kebab-case for custom event names (hydn-case between lowercase words, because unlike component and prop names, Vue does not do any case conversion on event names) for correct RECOGNITION by HTML

🎉 Vue 3 currently provides a emits option for components, similar to the options props, to define the events that a child component can trigger to its parent.

This option can accept a string as an array of elements or an object. You can set validators for events in objects, similar to the validators in the props definition.

🎉 In object syntax, the value of each property can be either NULL or the validation function that will receive the data passed when $emit is called. The validation function should return a Boolean value indicating whether the event parameter is valid.

const app = createApp({})

// Array syntax
app.component('todo-item', {
  emits: ['check'].created() {
    this.$emit('check')}})// Object syntax
app.component('reply-form', {
  emits: {
    // There is no validation function
    click: null.// with validation functions
    submit: payload= > {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload! `)
        return false}}}})Copy the code

It is strongly recommended to log the events that each component can fire with the option emits, because 🎉 Vue 3 has removed the. Native modifier, and for events not defined by the option emits in the child component, Vue will now use them as native event listeners (unlike Vue 2, Vue 2 considers event listeners that are added when using a component to listen for custom events, that is, only events that are emitted via $emit, to be added to the root element of the child component (unless inheritAttrs: False is set in the child component’s options).

<! Listen for custom events close and native events click -->
<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>

<script>
// component
app.component('MyComponent', {
  emits: ['close']})</script>
Copy the code

Use v-Models on components

When using custom input components, Vue optimizes them and also supports bidirectional binding using v-Models.

<custom-input v-model="searchText"></custom-input>

<! -- equivalent to the following code
<custom-input
  :model-value="searchText"
  @update:model-value="searchText = $event"
></custom-input>
Copy the code

Externally (on the label of the component when referencing the component) the variable to be synchronized is bidirectionally bound by the instruction V-Model, whose data is passed inside the component by default as a prop 🎉 named modelValue (so remember inside the component, You need to declare modelValue as prop in the props option, as the value property of the form element inside the child component.

It only needs to be handled within these components:

  • Use directives within componentsv-bindOf a form elementvalueProperty bound to the prop variable declared abovemodelValueOn, that is,v-bind:value="modelValue"(This allows you to “synchronize” the external data by taking the value passed in as the form’s value.)
  • Use directives within componentsv-onListening for form elementsinputEnter the event and passthrowupdate:modelValueThe event $emit('update:modelValue', $event.target.value)The corresponding data is passed outwards, and the external world receives the thrown event and responds to the instructionv-modelBound variables change based on thrown data(That is, the operation of data modification is still done in the parent layer)
// conpoment
app.component('custom-input', {
  props: ['modelValue'].emits: ['update:modelValue'].template: `  `
})
Copy the code
<custom-input v-model="searchText"></custom-input>
Copy the code

⚠️ Vue 2 provides the.sync modifier, with v-bind: propname. sync=”variable” and $emit(‘update:propName’, newValue) for bi-directional binding; But 🎉 was removed from Vue 3 with the.sync modifier, which is now implemented using a similar syntax (the component also throws events prefixed with update:).

The default prop variable passed is modelValue and the default to listen for thrown events is Update :modelValue. This method is more general and does not confuse the semantics of either type=”text” or type=checkbox. If you need to change the default value of modelValue, you can configure it in Vue 2 with the option model, but 🎉 has removed the option model in Vue 3. Instead, you can add parameters for V-model in the parent. The argument is the name of the prop variable and the name of the event to listen for (prefixed with update:).

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

<! -- is short for the following -->
<my-component :title="bookTitle" @update:title="bookTitle = $event" />
Copy the code
/ / component
app.component('my-component', {
  props: {
    title: String
  },
  emits: ['update:title'].template: `  `
})
Copy the code

In Vue 3, using a specific prop (by calling the component using the parameters of the directive V-Model) and throwing the corresponding events, 🎉 can create multiple V-Models on a single component, thus enabling bidirectional binding of multiple prop

<! -- use component to implement two-way binding between first-name and last-name -->
<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>
Copy the code
app.component('user-name', {
  props: {
    firstName: String.lastName: String
  },
  emits: ['update:firstName'.'update:lastName'].template: `   `
})
Copy the code

Using the directive V-model on input components not only supports the built-in Vue modifiers.trim,.number,.lazy, 🎉 Vue 3, but also supports adding custom modifiers. Modifiers added to the component’s V-model are provided to the component through the modelModifiers Prop.

The 💡 modifier should come after the argument of the directive, and the modifier supports multiple chaining

<! Capitalizing a string with a custom modifier -->
<my-component v-model.capitalize="myText"></my-component>
Copy the code
/ / component
app.component('my-component', {
  props: {
    modelValue: String.// modelModifiers Prop here we provide the initial value
    modelModifiers: {
      default: () = >({})}},emits: ['update:modelValue'].methods: {
    // Form input event handler
    emitValue(e) {
      let value = e.target.value
      Capitalize the form input value if the V-Model has a capitalize modifier
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)}// Throws an event
      this.$emit('update:modelValue', value)
    }
  },
  template: ` `.created() {
    // When a component's Created lifecycle hook fires, the modelModifiers Prop is set depending on how the component is called
    Because the V-Model modifier in this example is capitalize, the modelModifiers will include the capitalize attribute
    console.log(this.modelModifiers) // { capitalize: true }}})Copy the code

🎉 If the V-Model sets parameters, custom Modifiers added to the component V-Model will be provided to the component via prop in the form of arG + Modifiers.

<my-component v-model:description.capitalize="myText"></my-component>
Copy the code
app.component('my-component', {
  props: ['description'.'descriptionModifiers'].emits: ['update:description'].template: `  `.created() {
    console.log(this.descriptionModifiers) // { capitalize: true }}})Copy the code

The prop properties

Non-prop Attributes are attributes that are not explicitly declared in the props or 🎉 emits options of a component, but are passed to child components when a component is referenced. These attributes are added to the root element of the component by default. That is, on the

layer 1 of the container in the component’s template.

💡 If you want attributes to be added to a component’s non-root node, you can set the component’s inheritAttrs option: False, and then bind the special variable $attrs (which is an object containing all non-prop attributes) v-bind=”$attrs” to the specified node, 🎉 includes class, style, and V-on on this element in Vue 3. You can also specify an attribute binding :attributeName=”$attrs.propertyName”

app.component('date-picker', {
  inheritAttrs: false.template: ` 
      
`
}) Copy the code

💡 🎉 Non-prop attribute Vue 3 includes class, style, and V-ON. The $Listeners have been removed from Vue 3

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

⚠️ Because 🎉 Vue 3 supports components with multiple root nodes, unlike Vue 2, which only allows a single root component, components with multiple root nodes do not have automatic attribute fallback behavior, so a runtime warning will be issued if $attrs is not explicitly bound.

// This will issue a warning
app.component('custom-layout', {
  template: ` 
      
...
...
...
`
}) $attrs is passed to the
element without warning
app.component('custom-layout', {  template: `
...
...
...
`
}) Copy the code

slot

Slot slot is a set of content distribution apis implemented by Vue that are used by components as layouts.

Use the

element as a placeholder in the child component’s template

<! -- Todo-button component template -->
<button class="btn-primary">
  <slot></slot>
</button>
Copy the code

The content can then be passed to the location specified by the slot when the component is invoked

<todo-button>
  Add todo
</todo-button>
Copy the code

🔨 When a component is rendered, it replaces the slot with the given content, enabling content differentiation.

<! -- Render HTML -->
<button class="btn-primary">
  Add todo
</button>
Copy the code

💡 presets some default content in the slot tag of the child component template, which will only be rendered when no content is provided.

A named slot

If you want to distribute multiple content to different subcomponent nodes, you can set multiple named actions in the subcomponent template by adding the attribute name to the subcomponent’s tag

.

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

The

💡 The parameter default is the default slot. If the slot name is not specified in the differentiated content, the slot is placed in the default slot of the sub-component

<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>
Copy the code

💡 Sets the named slot directive v-slot:slotName with the abbreviation #slotName

<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>
Copy the code

⚠️ However, as with other directives such as V-bind :param :param and V-ON :eventName @eventName, this abbreviation is only available if it has arguments, which 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>

<! -- The right way -->
<todo-list #default="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>
Copy the code

Scope slot

Content that is partitioned in a parent component can only use data in its scope and cannot access data in a child component.

To enable the differentiated content to access the data only available in the child component, you need to bind the data to the corresponding properties of the slot tag

, called slot props, when the child component template sets up the slot

// Template for the child todo-list<ul>
  <li v-for="( item, index ) in items">
    <slot :item="item"></slot>
  </li>
</ul>
Copy the code

Then in the parent component, you can receive the data “thrown” by the child component with the value of the directive V-slot, which is an object containing all of the slot prop. In the example below, the object is named slotProps. You can use other names

<todo-list>
  <template v-slot:default="slotProps">
    <span class="green">{{ slotProps.item }}</span>
  </template>
</todo-list>
Copy the code

💡 If the component has only one slot, that is, the default slot, v-slot can be used directly on the component label when calling the component, that is, the label of the component can be used as the slot template

// Short form<todo-list v-slot:default="slotProps">
  <span class="green">{{ slotProps.item }}</span>
</todo-list>// even omit the default argument<todo-list v-slot="slotProps">
  <span class="green">{{ slotProps.item }}</span>
</todo-list>
Copy the code

⚠️ The above shorthand can only split content into one slot, and whenever multiple slots are present, always use the full

💡 Destructuring can be used to simplify code if the parent component uses only a few properties in the slot prop object when splitting content

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

<! Todo = todo; todo = todo; todo = todo;
<todo-list v-slot="{ item: todo }">
  <i class="fas fa-check"></i>
  <span class="green">{{ todo }}</span>
</todo-list>

<! -- Define alternate content for slot prop is undefined -->
<todo-list v-slot="{ item = 'Placeholder' }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>
Copy the code

Mixed with

Mixin refers to “extracting” the reusable portion of a component’s options into an object (which can contain computed, Method, Watch, and so on) that can be reused by other components. This object can then be reused by using it in the option mixins of components, including the root component; Or global mixin via app.mixin({}).

💡 But mixins are less flexible because they are prone to property name conflicts and cannot pass any parameters to mixins. To address these issues and make code reuse more efficient, Vue 3 has added a new way to organize code through logical concerns — the composite API.

// Define a mixin object
const myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin! ')}}}// Define a component that uses mixin objects
const Component = Vue.extend({
  mixins: [myMixin]
})

const component = new Component() // "hello from mixin!"
Copy the code

When components and mixins have options of the same name, these options are “merged” in the appropriate manner

The template reference

If you want to reference the COMPONENT’s DOM node after it has been mounted to the page, you can add the ref attribute to the component’s template and then access the corresponding element through the variable $refs.

For example, focus programmatically on the input while the component is mounted

const app = Vue.createApp({})

app.component('base-input', {
  template: `  `.methods: {
    focusInput() {
      this.$refs.input.focus()
    }
  },
  mounted() {
    this.focusInput()
  }
})
Copy the code

The ⚠️ variable $refs will only take effect after the component has rendered. This serves only as an escape hatch for direct manipulation of child elements. Direct manipulation of DOM nodes via $refs in templates or computed properties should be avoided, and data-driven DOM changes should be made instead.

💡 in Vue 2 the ref attribute used in v-for is the variable $refs will be the corresponding array. 🎉 In Vue 3, arrays for $ref are no longer created automatically in this case. If you want to get multiple refs (lists) in a single binding of V-for, you can bind ref to a function, passing DOM as an argument by default:

<div v-for="item in list" :ref="setItemRef"></div>
Copy the code
export default {
  data() {
    return {
      itemRefs: []}},methods: {
    setItemRef(el) {
      if (el) {
        this.itemRefs.push(el)
      }
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}
Copy the code

For the example above, itemRefs need not be an array, but can also be an object, and then the iterable’s key is set to ref; ItemRef can also be reactive and can be listened on if desired

Life cycle function

Each component goes through a series of initialization procedures when it is created: setting up data listeners, compiling templates, mounting instances to the DOM, updating the DOM as data changes, and so on. Vue also runs functions called lifecycle hooks along the way, giving users the opportunity to add their own code at different stages.

  • beforeCreate()Before the instance is generated
  • created()After the instance is generated, responsive data can be accessed
  • beforeMount()It is accessible after the template has been rendered, but before it has been mounted to the DOMapp.$el(includingappIs the virtual DOM of Vue instance
  • mounted()The instance is mounted to the DOM
  • beforeUpdate()The responsive data is transformed before the virtual DOM is re-rendered and updated
  • updated()The reactive data is transformed and the virtual DOM is re-rendered and updated
  • 🎉 beforeUnmount()When a Vue instance is destroyed, it is executed before destruction
  • 🎉 unmounted()When a Vue instance is destroyed, it is executed after destruction

💡 🎉 instances can be destroyed by calling the function app.unmount(), where app is a Vue instance. The related lifecycle functions are beforeUnmount() and unmounted().

The this context of the ⚠️ lifecycle hook points to the current active instance calling it, typically the component instance VM, so be careful not to use arrow functions on callbacks (and for option property) because arrow functions don’t have this, and this will always be looked up in the upper lexical scope as a variable. Errors may result.