Vue3 Preliminary Exploration (2) New Features
In this blog we continue to look at some of the new features of Vue3, including Teleport, snippets, and trigger component options.
Most of the content comes from the official doc, which explains and summarizes some difficult parts.
Teleport
Vue encourages us to build UIs by encapsulating them and related behaviors into components. We can nest them inside another to build a tree that makes up the application UI.
However, sometimes a part of a component template logically belongs to the component, and from a technical point of view, it is best to move this part of the template to another location in the DOM outside of the Vue app.
That is to say, we want ** to have a component that is logically a child of component A and can perform operations with A that normal parent child components can perform, but in fact it is rendered as a child of component B.
A common scenario is to create a component that contains full-screen mode. In most cases, you want the modal logic to exist in the component, but the positioning of the modal is quickly difficult to solve through CSS, or you need to change the component composition.
Consider the following HTML structure.
1 | <body> |
Let’s take a look at the modal-button component:
The component will have a button element to trigger the opening of the modal, and a div element with class .modal that will contain the content of the modal and a button for self-closing.
1 | const app = Vue.createApp({}); |
When using this component in the initial HTML structure, we can see a problem - the modal is rendered in deeply nested’div ‘, while the modal’position: absolute’ is referenced by the parent relative positioning’div '.
Teleport provides a clean method that allows us to control under which parent node HTML is rendered in the DOM without having to resort to global state or split it into two components.
Let’s modify’modal-button ‘to use’ < teleport > ‘and tell Vue "** Teleport ** this HTML ** to ** the’ ** body ** 'tag".
1 | app.component('modal-button', { |
Therefore, once we click the button to open the mode, Vue will correctly render the modal content as a child of the’body 'tag.
With
If < teleport > contains a Vue component, it will still be a logical child of the parent component:
1 | const app = Vue.createApp({ |
In this case, even if’child-component ‘is rendered in a different place, it will still be a child of’parent-component’ and will receive’name 'prop from it.
This also means that the injection from the parent component works as expected, and the child components will be nested under the parent component in Vue Devtools, rather than where the actual content is moved.
Fragment
In Vue 3, components now officially support multi-root node components, i.e. fragments!
2.x
In 2.x, multiple components are not supported, and a warning will be issued when the user accidentally creates multiple components. Therefore, to fix this error, many components are wrapped in a < div >.
1 | <!-- Layout.vue --> |
3.x
In 3.x, components can now have multiple root nodes! However, this does require developers to clearly define where attributes should be distributed.
1 | <!-- Layout.vue --> |
Inheritance of Non-Prop Attributes
A non-prop attribute is passed to a component, but the component does not have a corresponding attribute props Or emits Define attributes. Common examples include the’class’, ‘style’, and’id 'attributes.
Attribute
When a component returns a single root node, non-prop attributes are automatically added to the attributes of the root node. For example, in an instance of the < date-picker > component:
1 | app.component('date-picker', { |
If we need to define the state of the < date-picker > component via the data status property, it will be applied to the root node (i.e. div.date -picker).
1 | <! -- Date-picker component with non-prop attribute -- > |
The same rules apply to event listeners
On multiple root nodes
Unlike a single root node component, a component with multiple root nodes does not have automatic attribute fallback behavior. If ‘$attrs’ is not explicitly bound, a runtime warning will be issued.
1 | <custom-layout id="custom-layout" @click="changeValue"></custom-layout> |
1 | This will send a warning |
Custom events
Event name
Unlike components and props, there is no automatic case conversion for event names. Instead, the triggered event name needs to match exactly the name used to listen to the event. For example, if an event with the name camelCase is triggered:
1 | this.$emit('myEvent') |
Listening to the kebabe-case version of this name will have no effect:
1 | <! -- no effect -- > |
Unlike components and props, event names are not used as a JavaScript variable name or property name, so there is no reason to use camelCase or PascalCase. And the’v-on ‘event listener is automatically converted to all lowercase in the DOM template (because HTML is case-insensitive), so’ @myEvent ‘will become’ @myevent ‘- making’myEvent’ impossible to listen to.
Therefore, we recommend that you always use the event name of ** kebaby-case **
Define custom events
Emitted events can be defined on the component via the’emits’ option.
1 | app.component('custom-form', { |
When a native event (such as click) is defined in the emits option, the event in the component is used instead of the native event listener.
Validate events thrown
Similar to prop type validation, an emitted event can be validated if it is defined using object syntax instead of array syntax.
To add validation, the event is assigned a function that receives the arguments passed to the call to $emit and returns a boolean value indicating whether the event is valid.
1 | app.component('custom-form', { |
Multiple
By leveraging the ability to target specific props and events, as we previously demonstrated in v-model
参数As we learned in, we can now create multiple v-model bindings on a single component instance.
Each v-model will sync to a different prop without the need to add additional options in the component:
1 | <user-name |
1 | const app = Vue.createApp({}) |
Custom v-model modifier
In 2.x, we provide hardcoding support for modifiers such as .trim on component v-model. However, it is more useful if the component can support custom modifiers. In 3.x, modifiers added to component v-model will be provided to the component via modelModifiers prop:
Let’s create an example custom modifier capitalize that capitalizes the first letter of the string provided by the v-model binding.
Modifiers added to the component v-model will be provided to the component via modelModifiers prop. In the following example, we create a component that contains modelModifiers prop that defaults to an empty object.
Note that when the component’s created lifecycle hook is triggered, the modelModifiers prop contains capitalize, which has the value true - because it is set in the v-model binding v-model.capitalize = “bar”.
Now that we have the prop set up, we can check the modelModifiers object key and write a handler to change the emitted value. In the following code, we capitalize the string whenever the < input/> element triggers the input event.
1 | <div id="app"> |
1 | const app = Vue.createApp({ |
For a v-model binding with parameters, the resulting prop name will be arg + “Modifiers”:
1 | <my-component v-model:foo.capitalize="bar"></my-component> |
1 | app.component('my-component', { |