preface
Vue3.0 was officially launched in September last year, and everyone is hugging Vue3.0. New projects started using Vue3.0 earlier this year, and this article is a summary of the use of the new features of Vue3 and some experience sharing.
Why upgrade Vue3
In vue2.x, all data is defined in data and all methods are defined in methods, and we use this to call the corresponding data and methods. In vue3.x, we can not play this way, and we will talk about how to play it later. First, let’s talk about the flaws of vue2.x, so we will upgrade and change it.
Review the vue2.x implementation of addition and subtraction
<template> <div class="homePage"> <p>count: "> <p style=" max-width: 100%; clear: both; 10px" @click="increase"> </button> </button> </div> </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 implements the addition and subtracting of count and display multiples, so we need to perform operations in data, Methods and computed respectively. When we add a requirement, the following situation occurs:
When our business becomes complex, the above situation will occur in large numbers. As the complexity increases, a graph like this will appear, where each colored square represents a function:
Even one feature will 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, which can be painful to write.
Then we thought, if we could logically divide the above diagram into the following one, wouldn’t it be much clearer, thus making the code more readable and maintainable:
The solution given by vue2.x is mixins, but mixins also come with a troubling problem:
- Name conflict problem
- The role of exposed variables is unclear
- Logic reuse to other Components is often problematic
I will not give an example of the problems that often appear above, as people who have used them will encounter more or less. This article is not about mixins. If you really want to know, leave a comment
So, we created the Composition API at Vue3. X to solve this problem, combining scattered logic together for maintenance, and breaking up individual functional logic into separate files. Let’s focus on the Composition API.
Composition API
setup
Setup is an option added to Vue3. X. It is an entry point to use the Composition API within a component.
Setup Execution time
I saw many articles in the learning process that setup is between beforeCreate and created. This conclusion is wrong. Practice is the only criterion for testing truth.
export default defineComponent({ beforeCreate() { console.log("----beforeCreate----"); }, created() { console.log("----created----"); }, setup() { console.log("----setup----"); }});Copy the code
Setup execution time is executed before beforeCreate, detailed can see the life cycle behind.
The setup parameters
When using setup, it takes two arguments:
- Props: the attribute passed by the component
- context
The props accepted in setup are responsive, and are updated when new props are passed in. Since it is reactive, ES6 deconstruction cannot be used. Deconstruction removes its responsiveness. Examples of incorrect code that would make props no longer support responsiveness:
// demo.vue
export default defineComponent ({
setup(props, context) {
const { name } = props
console.log(name)
},
})
Copy the code
So in development, we want to use deconstruction while still keeping props responsive. Is there a way to do that? You can think about it and answer it in the later toRefs section. Setup does not have access to the most common this object in Vue2, so the context provides the three most common properties in this: Attrs, slot and Emit correspond to the $attr attribute, slot slot and $emit emit events in VUe2.x, respectively. And these properties are automatically synchronized to the latest value, so we get the latest value every time we use it.
Reactive, ref and toRefs
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 should they be used? Reactive is used for bidirectional binding of objects, and refs are used for bidirectional binding of base JS types. It’s easy for beginners to think that ref can handle basic JS types. For example, ref can also define bidirectional binding of objects.
setup() { const obj = ref({count:1, }) setTimeout(() =>{obj. Value. count = obj. Value. count + 1 obj.Copy the code
We will beobj.count
andobj.name
Binding to a page is also possible; butreactive
Functions can indeed represent an object, but they cannot represent basic types such as strings, numbers, Boolean, and so on. Let’s show it in coderef
,reactive
The use of:Operation effect:In the above code, we bind to the page byuser.name
.user.age
; It’s kind of tedious to write this way, but can we just writeuser
To deconstruct the properties in the The answer is nouser
Structure it so that it eliminates its response, so that’s what we’re talking about hereprops
You can’t use ES6 to deconstruct it directly. So if we want to use the deconstructed data, what’s the solutionusetoRefs
. ToRefs is used to convert a reactive object into a common object whose properties are all refs. The specific use mode 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); {nickname: "xiaofan", age: 26, gender: "nickname"}); setInterval(() => { year.value++; user.age++; }, 1000); Return {year, // use reRefs... toRefs(user), }; }}); </script>Copy the code
Lifecycle hook
We can see what the lifecycle hooks are by looking directly at the lifecycle diagram:From the figure we can see that Vue3.0 has been addedsetup
, which we also discussed in detail earlier, and then in Vue2. XbeforeDestroy
Change the name tobeforeUnmount
; destroyed
The table is moreunmounted
The authors say the change is purely semantic because a component is amount
andunmount
In the process. Other life cycles in Vue2 remain. aboveLife cycle chart
There are several other lifecycle hooks in theWe can see thatbeforeCreate
andcreated
besetup
(but you can still use it in Vue3 because Vue3 is backwards compatible, which means you’re actually using vuE2). Second, hook names have been addedon
; Vue3.x also adds a hook function for debuggingonRenderTriggered
andonRenderTricked
Let’s use a few simple hooks to help you learn how to use them. The hooks in Vue3. X are imported from vue:
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered } from "vue"; Export default defineComponent({// beforeCreate and created is 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
So far as life cycle is concerned, let’s introduce what’s different about watch in Vue3.
Watch and watchEffect
The watch function is used to listen to a specific data source and perform side effects in the callback function. The default is lazy, meaning that the callback is performed only when the source data being listened for changes.
watch(source, callback, [options])
Copy the code
Parameter description:
- Source: can support string, Object, Function, Array; Used to specify the response variable to listen on
- Callback: The callback function that is executed
- Options: Deep, immediate, and Flush options are supported.
I will show you how to use each of the three parameters, if you do not understand the use of watch please read below:
Listen to the 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);
// When the age value is changed, the watch callback is triggered
watch(
() = > state.age,
(curAge, preAge) = > {
console.log("The new value.", curAge, "The old values.", preAge); });return{... toRefs(state), }; }});Copy the code
Listen for data defined by the REF
const year = ref(0);
setTimeout(() = > {
year.value++;
}, 1000);
watch(year, (newVal, oldVal) = > {
console.log("The new value.", newVal, "The old values.", oldVal);
});
Copy the code
Listening to multiple data
In the above two examples, we use two watches respectively. When we need to listen to multiple data sources, we can merge them and listen to multiple data at the same time:
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, complex data can be seen everywhere, such as:
const state = reactive({
room: {
id: 100.attrs: {
size: "140 square meters".type: "Three rooms and two rooms.",}}}); watch(() = > state.room,
(newType, oldType) = > {
console.log("The new value.", newType, "The old values.", oldType);
},
{ deep: true});Copy the code
If you do not use the third parameter, deep:true, you will not be able to listen for data changes. As mentioned earlier, watch is lazy by default, so when is it not lazy to execute the callback immediately? It is easy to use immediate: true as the third parameter. The flush configuration is still learning and will be added later
Stop Stop listening
The watch listener we created in the component will automatically stop when the component is destroyed. If we want to stop a listener before the component is destroyed, we can call the return value of watch() and do the following:
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 listening function, watchEffect. In my opinion, Watch can already meet the needs of listening. Why do we need watchEffect? Although I did not get the necessity of it, I would like to introduce watchEffect. First, I would like to see the difference between its use and 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 execution result first prints the state and year values. Then every second, the state and year values are printed. As you can see from the code above, there is no need to pass in the dependencies first, as with Watch. WatchEffect collects the dependencies automatically, as long as a callback function is specified. When the component is initialized, it is executed once to collect the dependencies, and then the callback function is executed again when the data in the collected dependencies changes. So the comparison is summarized as follows:
- WatchEffect does not need to manually pass in dependencies
- WatchEffect is performed once to automatically collect dependencies
- WatchEffect cannot get the value before the change, only the value after the change
The Vue3 Composition API is partially introduced above, and there are many other very useful apis to use. Check out the Vue3 Composition API website. In fact, we can also do custom encapsulation.
Custom Hooks
In the beginning, we wrote an example of implementing addition and subtraction using vue 2.x. Here, we can wrap it as a hook. We agree that these “custom hooks” are prefixed with “use” to distinguish them from ordinary functions. 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);
constincrease = (delta? :number) :void= > {
if (typeofdelta ! = ="undefined") {
count.value += delta;
} else {
count.value += 1; }};const multiple = computed(() = > count.value * 2);
constdecrease = (delta? :number) :void= > {
if (typeofdelta ! = ="undefined") {
count.value -= delta;
} else {
count.value -= 1; }};return {
count,
multiple,
increase,
decrease,
};
}
Copy the code
Let’s take a look at using the useCount hook in a component:
<template>
<p>count: {{ count }}</p>
<p>Multiple: {{multiple}}</p>
<div>
<button @click="increase()">Add 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
In vue2.x, data,method,computed, etc. If you have just taken over the project, you cannot quickly associate data fields with method. Vue3, however, makes it more comfortable to combine the count related logic together. And useCount can be extended with more features. After the development of the project, I will write a summary of the “custom Hooks” used in the project, to help you more efficient development, about Composition API and custom Hooks introduced here, then a brief introduction to vue2.x and VUE3 response comparison.
A simple comparison of vue2.x and VUe3
In fact, before bate was released in vue3.x, it was a hot topic that Vue3.x would use Proxy instead of vue2.x’s object.defineProperty. There is no such thing as love or hatred without any reason or cause. Object. DefineProperty = Object. DefineProperty = Object. When I just started Vue2. X, I often encountered a problem, the data updated ah, why not the page update? Have you ever been stuck on when to update with $set and when to force with $forceUpdate? Later in the learning process, I began to contact the source code, and I knew that the root of everything was Object.defineProperty. Why Vue3.0 no longer uses defineProperty for data listening? For a more detailed explanation, here’s a quick comparison between object.defineProperty and Proxy
Object.defineProperty
You can only hijack the properties of an object, and a Proxy is a direct Proxy for an object
Since object.defineProperty can only hijack Object properties, each property of the Object needs to be traversed, or if the property value is also an Object, a recursive depth traversal is required. However, Proxy directly represents objects without traversal operations
Object.defineProperty
You need to add attributes manuallyObserve
Since object.defineProperty hijks the properties of the Object, when you add new properties, you need to walk through the Object again and hijack the new properties again using object.defineProperty. In vue2.x, when adding attributes to arrays and objects, you need to use $set to ensure that the new attributes are responsive. $set is also handled internally by calling object.defineProperty.
Teleport
Teleport is a new feature of Vue3. X. Translated is the meaning of transmission, may still feel I do not know so, nothing below I will give you a visual description.
What is a Teleport?
The Teleport is like the Doraemon “Any gate”, which is used to Teleport people to another place. With that in mind, let’s see why we need to use the Teleport feature. Let’s look at a small example: Using the Dialog component in the subcomponent Header, we often use the Dialog component in a similar situation in actual development, where the Dialog is rendered inside a layer of subcomponents, and it becomes difficult to handle the positioning, Z-index, and style of the nested components. Dialog should be a separate component from the dom structure, which should be completely stripped of the DOM mounted by Vue’s top-level components. You can also use the data or props value of the Vue component. In simple terms, you want to continue using dialogs inside the component, but you want to render DOM structures that are not nested in the component’s DOM. This is where the Teleport comes in. We can wrap the Dialog with the
. This creates a portal that will deliver the rendered Dialog content to any specified location. Here’s a small example of how Teleport is used
The use of the Teleport
We want the DOM rendered by Dialog to be sibling nodes to the top-level component, so we define an element for mounting in the index.html file:
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
Copy the code
Define a Dialog component, dialog.vue, noting the to property, which corresponds to 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
Finally, the Dialog component is used in a child component called header. vue. This section mainly demonstrates the use of Teleport, and the irrelevant code is omitted. The header component
<div class="header">
...
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>
...
Copy the code
Dom rendering looks like this:Image.png can be seen that we useteleport
Component, throughto
Property to specify where the component will be rendered and<div id="app"></div>
At the same level, which means at thebody
Next, butDialog
The state of thedialogVisible
It is completely controlled by internal Vue components.
Suspense
Suspense is a new feature in Vue3. X, so what’s it for? Don’t worry, let’s take a look at some scenes from VUe2.x to see how it works. In vue2.x you should often encounter a scenario like this:
<template> <div> <div v-if="! loading"> ... </div> <div v-if="loading"> </div> </div> </template>Copy the code
When the front and back end interact to get the data, it is an asynchronous process. We usually provide an animation in the load, and when the data is returned, v-IF controls the display of the data. Suspense should be familiar to you if you’ve used the vue-async-Manager plugin to complete the above requirements, Vue3. X feels like a reference to vue-async-manager. Vue3. X’s new built-in component Suspense offers two template slots that will initially render content in a fallback state. Formal content in default state is not rendered until a condition is reached, making it easier to show asynchronous renderings using the Suspense component. :::warning If used in Suspense, return a use of the Promise :::Suspense component:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
Copy the code
asyncComponent.vue
:
< span style = "box-sizing: border-box; color: RGB (74, 74, 74); font-size: 13px! Important; white-space: normal;" {{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 both default and Fallback states.
Fragment
In vue2.x, only one root node is allowed in the template:
<template>
<div>
<span></span>
<span></span>
</div>
</template>
Copy the code
But in Vue3. X, you can write multiple root nodes directly, isn’t that cool:
<template>
<span></span>
<span></span>
</template>
Copy the code
Better Tree – Shaking
Vue3.x refactored the global and internal API with tree-shaking in mind. The result is that the global API now needs to be referenced by ES Module. For example, in vue2.x, we used nextTick:
// vue2.x
import Vue from "vue"
Vue.nextTick(()=>{
...
})
Copy the code
NextTick () is a global API that is directly exposed from Vue objects, but $nextTick() is just a simple wrapper around vue.nextTick (), which binds the this callback function to the current instance for convenience. Although we are using the tree-shaking of WebPack, whether or not we actually use vue.nexttick () will end up in our production code, because the Vue instance is exported as a single object and the packager cannot insist on which attributes of the object are always used in the code. In Vue3. X it looks like this:
import { nextTick } from "vue"
nextTick(() =>{
...
})
Copy the code
Affected apis
This is a big change because previously global apis can now only be imported by name. This change will affect the following apis:
Vue.nextTick
Vue.observable
(useVue.reactive
Replace)Vue.version
Vue.compile
(Full version only)Vue.set
(Available in 2.x compatible versions only)Vue.delete
(Same as above)
Built-in tools
In addition to the API above, there are a number of built-in components that only apply to ES Modules Builds above, which are used to support tree-shaking bindings — the UMD build still includes all features and exposes everything on the Vue global variable (the compiler will generate the appropriate output, To use the API outside the global instead of importing). ::: What’s new in Vue3.0? What’s new in vue2.x?
change
Slot Specifies the syntax of a named slot
In vue2.x, a named slot is written:
<! --> <slot name="title"></slot>Copy the code
Used in parent components:
<template slot="title"> <h1>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:[" <slot name="content" :data="data"] }}}Copy the code
<! --> <template slot="content" slot-scope="scoped"> <div v-for="item in scoped. Data ">{{item}}</div> <template>Copy the code
In VUe2.x, named slots and scoped slots are implemented using slot and slot-scope, respectively. In Vue3.0, slot and slot-scope are combined. 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
Let’s first review the implementation of a custom instruction in Vue 2:
// Register a global custom directive 'v-focus' vue. directive('focus', {// when the bound element is inserted into the DOM... Omitted: function (el) {// insert element el.focus()}})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 do one-time initialization.
- Inserted: Called when the bound element is inserted into the parent node (only ensures that the parent exists, but not necessarily that it has been inserted into the document).
- Update: called when the component’s VNode is updated, but may occur before its children 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 more details on the hook function arguments).
- ComponentUpdated: component VNode directive and its children VNode all updated call.
- Unbind: called only once, when a directive is unbound from an element.
In Vue 3, the API for custom directives has been changed more semantically, just like the component lifecycle changes, for better semantics. The changes are as follows:So in Vue3, you can come from defining instructions like this:
const { createApp } from "vue"
const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
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
V – model upgrade
Before using Vue 3, we know that v-Model has changed a lot. After using Vue 3, we can really get these changes. Let’s review the changes and then talk about how to use it.
- Change: Used on custom components
v-model
The default name of the property and event is changed - Change:
v-bind
the.sync
In Vue 3, the modifier was removed and merged intov-model
In the - Added: Multiple components can be configured at the same time
v-model
- New: developers can customize
v-model
The modifier
Mentally? In Vue2, using a V-Model on a component is equivalent to passing a value attribute and triggering an input event:
<! -- Vue 2 --> <search-input v-model="searchValue"><search-input> <! -- equivalent --> <search-input :value="searchValue" @input="searchValue=$event"><search-input>Copy the code
In this case, the V-Model can only be bound to the component’s value property, so we are not happy. We would like to give our component a different property, and we do not want to update the value by triggering input. Before.sync comes out, Vue 2 does this:
Searchinput. vue export default {model:{prop: 'search', event:'change'}}Copy the code
After modification, the searchInput component with v-Model looks like this:
<search-input v-model="searchValue"><search-input> <! -- equivalent --> <search-input :search="searchValue" @change="searchValue=$event"><search-input>Copy the code
However, in actual development, we may need to “bi-bind” a prop in some scenarios. Here, the most common modal is used as an example: Modal is suitable for bi-directional binding of attributes, the external can control the visible display or hide of the component, the component internal close can control the visible property to hide, and the visible property is synchronously transmitted to the external. Inside the component, when we turn modal off, events are triggered in the child component in the update:PropName mode:
this.$emit('update:visible', false)
Copy the code
The parent component can then listen for this event for data updates:
<modal :visible="isVisible" @update:visible="isVisible = $event"></modal>
Copy the code
We can also use v-bind.sync to simplify the implementation:
<modal :visible.sync="isVisible"></modal>
Copy the code
The v-Model implementation and the bidirectional binding of component properties are reviewed in Vue2. How should this be implemented in Vue 3? In Vue3, using V-Model on a custom component is equivalent to passing a modelValue property and triggering an Update :modelValue event:
<modal v-model="isVisible"></modal> <! -- equivalent to --> <modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>Copy the code
To bind a property name, you only need to pass a parameter to the V-Model, and you can bind multiple V-Models at the same time:
<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
In Vue 3, async uses v-model, and async uses v-model
Asynchronous components
Vue3 uses defineAsyncComponent to define asynchronous components. The configuration option Component is replaced by Loader. The Loader function itself no longer accepts resolve and reject arguments and must return a Promise. The usage is as follows:
<template> <! Import {defineAsyncComponent} from "vue"; import {defineAsyncComponent} from "vue"; Export default {components: {// No configuration item 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
Reference article:
- Vue3 Family Bucket Starter guide
- Learn a wave of new Vue3 features
- New Features in Vue3.0 and a summary of using changes (for actual work)
- Great Vue3
idea
Take time to sort out the knowledge vein that is in line with the growth path of front-end engineers, share some experience summary to the industry as much as possible, and at the same time systematically review and consolidate themselves.
The code has been uploaded to Github for easy reading
If there are any mistakes in this post, please comment on them in the comments section. If it helps you, please like and follow ~~~