preface
Vue3.0 was officially released in September last year, and everyone is embracing Vue3.0 with enthusiasm. Earlier this year, new projects were developed using Vue3.0. This article is a summary of the new Vue3 features and some experience sharing.
In Vue2. X, all data are defined in data and methods are defined in methods, and this is used to call the corresponding data and methods. We will talk about how to play it later. First, we will talk about the defects of Vue2. X version written in this way, so that we will upgrade and change.
Review Vue2. X implementation addition and subtraction
<template> <div class="homePage"> <p>count: {{multiple}}</p> <p> <div> <button style="margin-right: 10px" @click="increase"> add 1</button> <button @click=" Decrease "> decrease 1</button> </div> </template> <script> export default { data() { return { count: 0 }; }, computed: { multiple() { return 2 * this.count; }, }, methods: { increase() { this.count++; }, decrease() { this.count--; ,}}}; </script>Copy the code
The above code only realizes the addition and subtraction of count and displays multiples, so it needs to operate in data, methods, and computed respectively. When we add a demand, the following situation will occur:
This happens a lot as we get more complex, and as complexity increases, we get a graph that looks like this, with squares of each color representing a function:
Even one feature may depend on other features, all mixed together.
When the component is more than a few hundred lines of code, adding or modifying a requirement requires repeated jumps in data, methods, computed, and Mounted, the pain of which has been well documented.
It would be much clearer if we could logically split the top graph into the bottom one, making the code more readable and maintainable:
The solution for vue2.x is mixins, but mixins can be annoying:
Naming conflicts are not clear. The logic used by exposed variables can be reused in other components. The point of this article is not mixins, so leave a comment if you really want to know
Therefore, Vue3. X introduced Composition API to solve the above problems, combining the scattered logic for maintenance, and separating the separate functional logic into separate files. Let’s focus on the Composition API.
Composition API
Setup Setup is a new option in Vue3. X, which is the entry point for using the Composition API within components.
Setup Execution timing
I’ve seen a lot of articles that say setup is between beforeCreate and created, which is wrong. Practice is the sole criterion for testing truth, so I went to test it myself:
export default defineComponent({ beforeCreate() { console.log("----beforeCreate----"); }, created() { console.log("----created----"); }, setup() { console.log("----setup----"); }});Copy the code
The setup execution is performed before the beforeCreate. For details, see the lifecycle.
The setup parameters
When you use setup, it takes two arguments:
The props received in context Setup is reactive. When new props are passed in, they are updated. Because it is reactive, ES6 deconstruction cannot be used, because deconstruction eliminates its reactive. Example of incorrect code for this rule that causes props to no longer support reactive:
1. // demo.vue
2. export default defineComponent ({
3. setup(props, context) {
4. const { name } = props
5. console.log(name)
6. },
7. })
Copy the code
Is there any way we can use deconstruction in development and still keep the props responsive? You can think about it and answer it later in the toRefs section. Context is the second argument that setup accepts. We said that setup does not have access to this, the most commonly used object in Vue2, so context provides the three most commonly used properties of this: Attrs, slot, and emit correspond to the attr attribute, slot slot and attr attribute, slot slot and attr attribute, slot slot and EMIT event in vue2. x respectively. And these attributes are automatically synchronized with the latest values, so we get the latest values every time we use them.
In VUe2. X, data is defined in data, but Vue3. X can use Reactive and ref to define data. So what’s the difference between ref and Reactive? When will they be used? Reactive handles bidirectional binding of objects, whereas REF handles bidirectional binding of js base types. It is easy for beginners to think that ref can handle js primitives. For example, ref can also define object bidirectional bindings.
setup() { const obj = ref({count:1, SetTimeout (() =>{obj.value.count = obj.value.count + 1 obj.value.name = "obj "}, 1000) return{obj}}Copy the code
We can also bind obj.count and obj.name to the page; But reactive does delegate an object, but not basic types such as strings, numbers, Boolean, and so on. Ref and Reactive are used in the following code:
In the above code, we bind to the page through user.name,user.age; This feels cumbersome, but can we just deconstruct the attributes in user and use them? You can’t structure the user directly, because that would eliminate its responsiveness. (props cannot be destructed using ES6. What we wanted to do with the deconstructed data, and the solution was toRefs. ToRefs is used to convert a Reactive object to a normal object whose properties are all REF objects. The specific usage is as follows:
< the template > < div class = "homePage" > < p > the first {{year}}, < / p > < p > name: {{nickname}} < / p > < p > age: {{ age }}</p> </div> </template> <script> import { defineComponent, reactive, ref, toRefs } from "vue"; export default defineComponent({ setup() { const year = ref(0); Reactive ({nickname: "xiaofan", age: 26, gender: "female"}); setInterval(() => { year.value++; user.age++; }, 1000); Return {year, // use reRefs... toRefs(user), }; }});Copy the code
Lifecycle hook
We can look directly at the lifecycle diagram to see which lifecycle hooks are available:
We can see from the figure that Vue3.0 added setup, which we discussed in detail earlier, and then changed the beforeDestroy name in vue2. x to beforeUnmount; The destroyed table is more unmounted. The author states that the change is purely to be more semantic because a component is a mount and unmount process. Other Vue2 life cycles remain. The above lifecycle diagram does not contain all lifecycle hooks. There are several other lifecycle hooks, as shown below:
We can see that beforeCreate and Created have been replaced by setup (but you can still use them in Vue3 because Vue3 is backward compatible, meaning you’re actually using vue2). Second, hook names add on; If Vue3. X is used to package hooks, onRenderTriggered and onrendertricked. vue is used to package hooks.
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered } from "vue"; Export default defineComponent({// beforeCreate and created are vue2 beforeCreate() {console.log("------beforeCreate-----"); }, created() { console.log("------created-----"); }, setup() { console.log("------setup-----"); / / vue3. Write x life cycle in the setup onBeforeMount (() = > {the console. The log (" -- -- -- -- -- - the onBeforeMount -- -- -- -- -- "); }); onMounted(() => { console.log("------onMounted-----"); }); OnRenderTriggered ((event) =>{console.log("------onRenderTriggered-----",event); })}});Copy the code
That’s all for the content related to the life cycle. Now let’s introduce the differences of Watch in Vue3.
watch(source, callback, [options])
Copy the code
Parameter Description:
Source: can support string, Object, Function, Array; Callback: Callback function to execute options: Supports deep, immediate, and flush options. Next, I will introduce how these three parameters are used respectively. If you don’t understand the use of watch, please read below:
Listen to data defined by Reactive
import { defineComponent, ref, reactive, toRefs, watch } from "vue"; export default defineComponent({ setup() { const state = reactive({ nickname: "xiaofan", age: 20 }); setTimeout(() => { state.age++; }, 1000); Watch (() => state.age, (curAge, preAge) => {console.log(" new value :", curAge, "old value :", preAge); }); return { ... toRefs(state), }; }});Copy the code
Listen for data defined by ref
const year = ref(0); setTimeout(() => { year.value++; }, 1000); Watch (year, (newVal, oldVal) => {console.log(" new value :", newVal, "old value :", oldVal); });Copy the code
Listening for multiple data
In the above two examples, we use two Watches respectively. When we need to listen to multiple data sources, we can merge and listen to multiple data simultaneously:
Watch ([() = > state. The age, year], [[curAge, newVal], [preAge oldVal]) = > {the console. The log (" new values: "curAge," old value: ", preAge); Console. log(" new value :", newVal, "old value :", oldVal); });Copy the code
Listen for complex nested objects
In our actual development, we see complex data everywhere, such as:
Const state = reactive({room: {id: 100, attrs: {size: "140 m2 ", type: "140 m2 ",},},}); Watch (() => state.room, (newType, oldType) => {console.log(" new value :", newType, "old value :", oldType); }, { deep: true } );Copy the code
You cannot listen for data changes without using the third parameter deep:true. As we mentioned earlier, watch is lazy by default, so when is it not lazy and the callback can be executed immediately? Set the third parameter to immediate: true. Flush configuration, still learning, will be added later
Stop Stop listening
The Watch listener we created in the component will stop automatically when the component is destroyed. If we want to stop a listener before the component is destroyed, we can call the return value of the watch() function as follows:
Const stopWatchRoom = watch(() => state.room, (newType, oldType) => {console.log(" new value :", newType, "old value :", oldType); }, {deep:true}); SetTimeout (()=>{// stop listening stopWatchRoom()}, 3000)Copy the code
There is also a watchEffect function. In my opinion, Watch can meet the needs of monitoring, so why there is a watchEffect function? Although I didn’t get the need for it, I still want to introduce the watchEffect, first of all, to see how it is different from Watch.
import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue";
export default defineComponent({
setup() {
const state = reactive({ nickname: "xiaofan", age: 20 });
let year = ref(0)
setInterval(() =>{
state.age++
year.value++
},1000)
watchEffect(() => {
console.log(state);
console.log(year);
}
);
return {
...toRefs(state)
}
},
});
Copy the code
The state and year values are printed first. Then, every second, print the state and year values. As you can see from the code above, unlike Watch, dependencies are not passed in first. WatchEffect collects them automatically by specifying a callback function. At component initialization, dependencies are collected once, and then callbacks are executed again when the data in the collected dependencies changes. So the comparison is as follows:
WatchEffect does not need to be manually passed in as a dependency. WatchEffect is executed once to automatically collect the value before the dependency changes. You can only get the changed value. The Vue3 Composition API is available in the section above. In fact, we can do custom encapsulation.
In the beginning we wrote an example of adding and subtracting using vue2. x. Here we can wrap it as a hook. We agreed that these “custom Hooks” are distinguished from ordinary functions by using the prefix use. UseCount. Ts implementation:
import { ref, Ref, computed } from "vue"; type CountResultProps = { count: Ref<number>; multiple: Ref<number>; increase: (delta? : number) => void; decrease: (delta? : number) => void; }; export default function useCount(initValue = 1): CountResultProps { const count = ref(initValue); const increase = (delta? : number): void => { if (typeof delta ! == "undefined") { count.value += delta; } else { count.value += 1; }}; const multiple = computed(() => count.value * 2); const decrease = (delta? : number): void => { if (typeof delta ! == "undefined") { count.value -= delta; } else { count.value -= 1; }}; return { count, multiple, increase, decrease, }; }Copy the code
Let’s look at using the useCount hook in a component:
<template> <p>count: {{count}}</p> <p> {{multiple}} < / p > < div > < button @ click = "happens ()" > 1 < / button > < button @ click = "decrease ()" > minus one < / button > < / div > </template> <script lang="ts"> import useCount from ".. /hooks/useCount"; setup() { const { count, multiple, increase, decrease } = useCount(10); return { count, multiple, increase, decrease, }; }, </script>Copy the code
At the beginning, vue2. x is scattered in data,method,computed, etc. If you just take over the project, you really cannot quickly associate the data field with method, but Vue3 clearly shows that it looks much more comfortable to aggregate the count-related logic together. And useCount can extend even more. Following the development of vue2. X, there will be a follow-up article that summarizes the “custom Hooks” that were used in the project to help you develop more efficiently. Here are the Composition API and custom Hooks, and a brief introduction to vue2.
In fact, before the release of bate in vue3.x, one of the hot topics was that vue3.x would use Proxy instead of Object. DefineProperty in vue2.x. There is no such thing as love or hatred without cause or reason. Why Object. DefineProperty should be changed? We can talk about it briefly. When I just started using Vue2. X, I often encountered a problem: data update ah, why the page is not updated? Did you get stuck when to update with set, when to update with set, when to update with set, when to force updates with forceUpdate? Later in the learning process, I started to contact the source code and realized that the root of everything was Object.defineProperty. If you want to know more about this, you can read this article. Why did Vue3.0 stop using defineProperty for data listening? To explain in detail in another article, here’s a quick comparison between Object.defineProperty and Proxy
Object.defineproperty can only hijack Object attributes, while Proxy is a direct Proxy Object. Because Object.defineProperty can only hijack Object attributes, every attribute of the Object needs to be traversed. If the attribute value is also an Object, the recursive deep traversal is required. However, Proxy directly proxies objects without traversal
Object.defineproperty jacks the attributes of the Object. When adding attributes, you need to traverse the Object again. Its new attribute is hijacked again using Object.defineProperty. When you add new attributes to arrays and objects in Vue2. X, You need to use set to ensure that new attributes are responsive,set to ensure that new attributes are responsive, and the inside of a set is handled by calling Object.defineProperty.
Teleport Teleport is a new feature of Vue3. X. Translation is the meaning of transmission, may still feel I do not know so, nothing below I will give you the image of the description.
What is a Teleport? Teleport is like the “any door” in Doraemon. The function of any door is to Teleport people to another place in an instant. With this in mind, let’s see why we need to use the Teleport feature. Here’s a small example: Using a Dialog component in the Header child component makes it difficult to handle the positioning, z-index, and styling of nested components in similar situations that we often use in real development. Dialog should be an independent component from the user perception level, and the DOM structure should be completely stripped of the DOM mounted by the Vue top-level component. You can also use the value of the state (data or props) within the Vue component. Simply put, you want to continue to use dialogs inside the component, but you want to render DOM structures that are not nested inside the component’s DOM. This is where Teleport comes in, we can wrap Dialog, and now a portal is set up to send Dialog’s rendered content to any specified location. Here’s a quick example of how Teleport can be used
We want the dom rendered by the Dialog to be sibling to the top-level component, defining a mount element in the index. HTML file:
1. <body>
2. <div id="app"></div>
3. <div id="dialog"></div>
4. </body>
Copy the code
Define a Dialog component dialog. vue, note that the to attribute is the same as the id selector above:
<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{ title }}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template>
Copy the code
Dom rendering looks like this:
As you can see, we use the Teleport component and specify the rendering position of the component with the to attribute
Suspense is a new feature in Vue3. X, so what’s it good for? Don’t worry, let’s look at some of the scenarios in ve2. X to see what it does. You should often encounter a scenario like this in Vue2. X:
1. <template> 2. <div> 3. <div v-if="! loading"> 4. ... 5. </div> 6. <div v-if="loading"> 7. Loading in... 8. </div> 9. </div> 10. </template>Copy the code
It is an asynchronous process when the front and back end interact to obtain data. Generally, we will provide a loading animation and control the data display with V-IF when the data is returned. Suspense is probably familiar if you’ve used vue-Async-Manager to meet the requirements above, Vue3.x feels like a reference to vuue-async-manager.vue3. x’s new built-in component Suspense, which provides two template slots to render a fallback state at first. The formal content of the default state is not rendered until some condition is reached, and it is easier to show asynchronous rendering using Suspense components. ::: Warning If Suspense is used, return a promise :::Suspense component use:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
Copy the code
asyncComponent.vue
:
<<template> <div> <h4> This is an asynchronous load data </h4> <p> user name: {{user.nickname}}</p> <p> Age: {{user.age}}</p> </div> </template> <script> import { defineComponent } from "vue" import axios from "axios" export default defineComponent({ setup(){ const rawData = await axios.get("http://xxx.xinp.cn/user") return { user: rawData.data } } }) </script>Copy the code
In the code above, Suspense is just a component with slots, except that slots specify the default and fallback states.
Fragment
In ve2. X, only one root node is allowed in the template:
1. <template>
2. <div>
3. <span></span>
4. <span></span>
5. </div>
6. </template>
Copy the code
But in Vue3. X, you can write multiple root nodes directly. Isn’t that cool?
1. <template>
2. <span></span>
3. <span></span>
4. </template>
Copy the code
Better Tree – Shaking
Vue3. X reconstructs global and internal apis with tree-shaking in mind. The result is that global apis now require named references via ES Module references, for example in Vue2.
1. // vue2.x 2. import Vue from "vue" 3. 4. Vue.nextTick(()=>{ 5. ... 6.})Copy the code
This is a major change, as the previous global API can now only be imported by named, and this change affects the following apis:
Vue.nextTick
Ue. Observable (replaced by ue. Reactive)
Vue.version
Vue.compile (available only in full version)
Vue.set (only available in 2.x compatible versions)
Vue.delete (same as above)
Built-in tools
In addition to the API above, there are many built-in components that are only available in ES Modules builds for tree-shaking enabled binders — UMD builds still include all features and expose all content on Vue global variables (the compiler will generate the appropriate output, To use an out-of-global API instead of an import). ::: Here are some of the new features of Vue3.0. What are the changes compared to vue2.x?
Change slot named slot syntax in Vue2. X, named slot is written:
1. <! <slot name="title"></slot>Copy the code
Used in the parent component:
1. <template slot="title"> 2.Copy the code
If we want to bind data to a slot, we can use a scoped slot as follows:
// subcomponent <slot name="content" :data="data"></slot> export default {data(){return{data:[" Go through people come and go "," don't like to enjoy "," company is the most love "] }}}Copy the code
1. <! <template slot="content" slot-scope="scoped"> 3. <div v-for="item in scoped. Data ">{{item}}</div> 4. <template> 5.Copy the code
In vue2. x, named slot and scoped slot are implemented using slot and slot-scope respectively. In Vue3.0, slot and slot-scope are combined and approved. The v – slot Vue3.0:
<! --> <template v-slot:content="scoped"> <div v-for="item in scoped. Data ">{{item}}</div> </template> <! - can also be abbreviated as: -- -- > < # template content = "{data}" > < div v - for = "item in the data" > {{item}} < / div > < / template >Copy the code
Custom instruction
First, let’s review the implementation of a custom directive in Vue 2:
Directive ('focus', {3. // When the bound element is inserted into the DOM... Inserted: function (el) {5. // Focus element 6.el.focus () 7.} 8.})Copy the code
In Vue 2, custom directives are created with the following optional hooks:
Bind: Called only once, the first time a directive is bound to an element. This is where you can perform one-time initialization Settings.
Inserted: Called when the bound element is inserted into a parent (the parent is guaranteed to exist, but not necessarily inserted into the document).
Update: called when the component’s VNode is updated, but may occur before its child VNodes are updated. The value of the instruction may or may not have changed. However, you can ignore unnecessary template updates by comparing the values before and after the update (see below for detailed hook function parameters).
ComponentUpdated: Invoked when the VNode of the component where the directive resides and its child VNodes are all updated.
Unbind: Called only once, when an instruction is unbound from an element.
In Vue 3, more semantic changes have been made to the API of custom directives, just like component lifecycle changes, for better semantics. The changes are as follows:
So in Vue3, you can customize instructions like this
1. const { createApp } from "vue"
2.
3. const app = createApp({})
4. app.directive('focus', {
5. mounted(el) {
6. el.focus()
7. }
8. })
Copy the code
You can then use the new V-focus directive on any element in the template, as follows:
<input v-focus />
Copy the code
Before using Vue 3, we know that the V-Model has changed a lot. After using Vue 3, we can get the real changes. Let’s take a look at the changes and then discuss how to use them.
Changed: The default names of properties and events have been changed when using v-models on custom components. Changed: the.sync modifier for V-bind was removed in Vue 3 and incorporated into v-Models. New: Multiple V-Models can be set for the same component. Developers can customize V-Model modifiers In Vue2, using a V-Model on a component actually passes a value attribute and fires an input event:
<! -- Vue 2 --> <search-input v-model="searchValue"><search-input> <! < searchValue :value="searchValue" @input="searchValue=$event">Copy the code
The v-Model can only be bound to the value property of the component, so we are not happy. We might as well have another property for our component, and we don’t want to update the value by firing the input, which is implemented in Vue 2 before async comes out:
Export default {3. Model :{4. Prop :' search', 5. Event :'change' 6.Copy the code
After modification, the searchInput component using the V-Model looks like this:
<search-input v-model="searchValue"><search-input> <! <search-input :search="searchValue" @change="searchValue=$event"><search-input>Copy the code
However, in actual development, there are some scenarios where we may need to “bidirectional bind” a prop. Here we use the most common modal example: Modal is very suitable for bidirectional binding of attributes. The external can control the visible display or hiding of components, and the internal closing of components can control the visible property hiding, and the visible property is transmitted to the external simultaneously. Inside the component, when we turn Modal off, events are triggered in the update:PropName mode in the child component:
this.$emit('update:visible', false)
Copy the code
You can then listen for this event in the parent component for data updates:
<modal :visible="isVisible" @update:visible="isVisible = $event"></modal>
Copy the code
We can also use v-bind.async to simplify the implementation:
<modal :visible.async="isVisible"></modal>
Copy the code
We reviewed the V-Model implementation in Vue2 and the bidirectional binding of component properties, so how should this be implemented in Vue 3? In Vue3, using v-Models on custom components is equivalent to passing a modelValue property and firing an Update :modelValue event:
1. <modal v-model="isVisible"></modal> 2. <! <modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>Copy the code
To bind the attribute name, you only need to pass a single parameter to the V-Model. Multiple V-Models can be bound simultaneously:
<modal v-model:visible="isVisible" v-model:content="content"></modal> <! -- equivalent to --> <modal :visible="isVisible" :content="content" @update:visible="isVisible" @update:content="content" />Copy the code
Vue 3: Vue 3: Async: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue 3: Vue
Vue3 defines asynchronous components using defineAsyncComponent. The configuration option component is replaced by loader. The loader function no longer accepts resolve and reject. And you must return a Promise as follows:
<template> <! Use of asynchronous components --> <AsyncPage /> </tempate> <script> import {defineAsyncComponent} from "vue"; Export default {components: {// None AsyncPage: DefineAsyncComponent (() => import("./ nextPage.vue ")), // AsyncPageWithOptions: defineAsyncComponent({loader: () => import(".NextPage.vue"), delay: 200, timeout: 3000, errorComponent: () => import("./ErrorComponent.vue"), loadingComponent: () => import("./LoadingComponent.vue"), }) }, } </script>Copy the code
Refer to the article: “King thirteen” CSDN blogger original article The original link: blog.csdn.net/sqLeiQ/arti…