It has been a while since VUe3 came out. At present, the author has officially developed two projects with VUe3 in the company, and both of them have been successfully launched for a period of time. Now I will make a simple summary of VUe3 based on my experience of using vue3 during this period and my understanding of learning VUe3.
Vue3
faster
According to the official statement, vuE3 is 1.2 to 2 times better than VUE2 in terms of performance, including the following optimizations
The diff algorithm
Vue3 has optimized diff algorithm and added PatchFlags to dynamically mark the parts that need to be updated. PatchFlags can specifically mark which parts of elements may be updated, as follows:
export const enum PatchFlags {
TEXT = 1.// Dynamic text content
CLASS = 1 << 1, OB10 / / dynamic class
STYLE = 1 << 2, OB100 // Dynamic style
PROPS = 1 << 3, OB1000 / / dynamic props
FULL_PROPS = 1 << 4, OB10000 // There is a dynamic key, that is, the key of the props object is not certain
HYDRATE_EVENTS = 1 << 5, OB100000 // Merge events
STABLE_FRAGMENT = 1 << 6, OB1000000 // children fragment in order
KEYED_FRAGMENT = 1 << 7, OB10000000 // children fragment with key node
UNKEYED_FRAGMENT = 1 << 8, OB100000000 // Fragment of children without key
NEED_PATCH = 1 << 9, OB1000000000 // Only non-props need patch, such as' ref '
DYNAMIC_SLOTS = 1 << 10, OB10000000000 // Dynamic slots
// The following are special flags, which are not used in optimization. They are built-in special flags
// Indicates that it is a static node, its content never changes, and there is no need to diff its children during hydrate
HOISTED = -1.// The diff used to indicate a node should end
BAIL = -2,}Copy the code
Before, VUe2 performed diff comparing all nodes, and then updated the nodes with changes
Vue3 will mark elements first and mark the parts that need to be updated (for example, one element is dynamic only and the others are static, then mark PatchFlags of class). During diff, only the parts that need to be updated are compared, not all of them, thus optimizing performance.
Event caching
Vue3 also does cacheHandlers, which means that if the event bound to an element does not change, the event is cached and the next time you create a render element, you use the cached event directly
But the store doesn’t respond. Here’s the code for the vue3 compilation site (vue-template-explorer.net lify.app/#/)
<button @click="clickFun">button</button>
Copy the code
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("button", {
onClick: _cache[0] || (_cache[0] = (. args) = >(_ctx.clickFun && _ctx.clickFun(... args))) },"Button"))}Copy the code
Static ascension
Vue3 determines which elements are static and should not be updated, such as the.title element below. These elements are promoted by Vue and are created only once. Subsequent updates are not recreated, but simply reused to save performance. In VUe2, elements are recreated every time they are rendered, whether or not they need to be recreated.
<template>
<div>
<div class='title'>Static data</div>
<div>
{{ data }}
</div>
</div>
</template>
Copy the code
Of course, the performance optimization of VUe3 is more than these, here are just a few representative, and vue3 is still in the upgrade, optimization
smaller
Vue3 is compiled on demand, and the global and internal apis have been refactored to support tree-shaking.
Many of the apis used by VUe2 in the past were globally mounted on a single Vue object and used through Vue when needed. Or this. But sometimes, for small projects, we may not use many of the other apis in Vue, so these unused apis should not have been packaged in our final project, when VUe2 was packaged together. With VUE3, we can specify which features we want to use by importing, for example
import { nextTick } from 'vue'
Copy the code
In this way, other unused apis are cleaned up by tree-shaking in the final packaging to achieve a smaller packaging volume.
The support of the typescript
Vue3 is written in typescript and supports typescript naturally. If you want TypeScript to correctly infer types in Vue component options, you need to define components using the defineComponent global method.
import { defineComponent } from 'vue'
const Component = defineComponent({
// Type inference is enabled
})
Copy the code
The life cycle
Life cycle diagrams of VUe2 and VUe3
A comparison of the above pictures reveals the following differences
We look down from the top
Create instance differences
Vue2 and VUe3 have changed a lot in terms of instance creation, so let’s look at the code below
-
Vue2 Create an instance
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' new Vue({ router, store, render: (h) = > h(App), }).$mount('#app') Copy the code
-
Vue3 Create an instance
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; createApp(App).use(ElementPlus).use(store).use(router).mount("#app"); Copy the code
As you can see from the code, vue2 uses new to create Vue instances. Vue is a constructor, and objects constructed from Vue share all global configuration
import GlobalButton from './components/GlobalButton.vue'
Vue.compoent('global-button', GlobalButton)
const appA = new Vue().$mount('#app-A')
const appB = new Vue().$mount('#app-B')
Copy the code
As the code above shows, the global-button component has been registered globally on Vue, which can be used by appA and appB. However, we cannot register global-Button component globally only in appA. Therefore, in a sense, VUe2 does not have the concept of application.
In the vue3
import { createApp } from "vue";
import appA from "./appA.vue";
import appB from "./appB.vue";
import GlobalButton from './components/GlobalButton.vue'
const appA = createApp(appA)
appA.component('global-button', GlobalButton)
appA.mount("#app-A");
const appB = createApp(appB).mount("#app-B");
Copy the code
The createApp factory function returns the application instance that provided the application context, allowing chain-linking. Note: Mount comes last, because mount does not return the application itself, it returns the root component instance.
This instance is independent from and unaffected by other instances.
From the “el” option? The difference between
As can be seen from the figure, the figure of VUe2 Has a Has’ el ‘option? $mount(el); vue2 determines if the created element is mounted on the el element ($mount(el)) after the beforeCreate and created elements are created. You can see the EL in the first block (app.mount(el)), which means vue3 starts when everything is ready.
El processing difference
Go to “Has’ template ‘option? In a branch of NO:
Vue2 is Compile el’s outerHTML as template. Vue3 is Compile el’s innerHTML as template.
The outerHTML contains the tag of the element itself. The outerHTML contains the tag of the element itself
<div id="app">
<span>The text</span>
</div>
<script>
let app = document.querySelector('#app')
console.log(app.outerHTML) //
console.log(app.innerHTML) //
</script>
Copy the code
So as we go down the life cycle diagram we can see that
Vue2 is the way to Create vm.$el and replace “el” with it
Vue3 is the way to Create app.$el and append it to EL which is the way to insert
Comparing vue2 and vue3 projects created by vuecli, we can see that the app. vue file vue2 has a div element with the ID App, while vue3 does not
// vue2 app.vue file<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default{}</script>// vue3 app. vue file<template>
<router-view />
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
});
</script>
Copy the code
Component destruction lifecycle differences
We can see from the figure that beforeDestroy is renamed to beforeUnmount; Destroyed is renamed unmounted
Modular API
One of the major updates of VUE3 is that, unlike vue2’s optional API consisting of data, methods, watch, etc., composite API can improve the flexibility and reuse of code in a freer and more native writing style, which is particularly important in large projects.
Take a look at the following simple examples of VUe2 and VUe3
// vue2
<template>
<div>
<div class="">
{{ fruit }}
</div>
<el-button @click="changeFruit">Modify the values of fruit</el-button>
<div class="">
{{ animal }}
</div>
<el-button @click="changeAnimal">Modify the value of animal</el-button>
</div>
</template>
<script>
export default {
data() {
return {
fruit: 'apple'./ / logic 1
animal: 'monkey'.2 / / logic}},methods: {
changeFruit() {
this.fruit = 'banner'
}, / / logic 1
changeAnimal() {
this.animal = 'tiger'
}, 2 / / logic}},</script>
Copy the code
// vue3
<template>
<div>
<div class="">
{{ fruit }}
</div>
<el-button @click="changeFruit">Modify the values of fruit</el-button>
<div class="">
{{ animal }}
</div>
<el-button @click="changeAnimal">Modify the value of animal</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
let fruit = ref<string>("apple"); / / logic 1
function changeFruit() {
fruit.value = "banner";
} / / logic 1
// ------------------------------------------
let animal = ref<string>("monkey"); 2 / / logic
function changeAnimal() {
animal.value = "tiger";
} 2 / / logic
return{ fruit, changeFruit, animal, changeAnimal, }; }});</script>
Copy the code
// It can also be written like this<script lang="ts">
import { defineComponent, ref } from "vue";
function fruitSetup(){
let fruit = ref<string>("apple"); / / logic 1
function changeFruit() {
fruit.value = "banner";
} / / logic 1
return {
fruit,
changeFruit
}
}
function animalSetup(){
let animal = ref<string>("monkey"); 2 / / logic
function changeAnimal() {
animal.value = "tiger";
} 2 / / logic
return {
animal,
changeAnimal
}
}
export default defineComponent({
setup() {
return{... fruitSetup(), ... animalSetup(), }; }});</script>
Copy the code
It can be found that the combined API can get rid of the constraint of data, methods and other options, and write the same logical code together, which is convenient for subsequent maintenance and iteration. The example above is just a very simple example, in the daily development, especially in large projects, a large component logic may have a lot of concerns, at this point, the option type API will increase the difficulty that code reading, make it difficult to maintain, logical separation also hides the potential logic problem, increase the hidden risk. The advent of composite apis solves these problems.
Refer to the following figure for logical concerns divided by color
setup
Setup is a component option, an entry function to the composite API, and is executed after the beforeCreate hook (before the component is created) is resolved.
This is called undefined in the setup function. So you don’t have to write as much “this” as vue2 does.
So why vue2 needs this? Because vue2 is limited by scope, composite API can get rid of this limitation, so that we can combine variables, functions and so on into the required components in a more flexible and free way, and further improve the code reuse.
<template>
<div @click="changeDataText">
{{ dataText }}
</div>
<div @click="changeDataInfo">
{{ dataInfo }}
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { commonSetup, commonSetupSecond } from '@/common/commonSetup.ts'
export default defineComponent({
setup(){
const { dataText, changeDataText } = commonSetup()
const { dataInfo, changeDataInfo } = commonSetupSecond()
return {
dataText,
changeDataText,
dataInfo,
changeDataInfo
}
},
})
</script>
Copy the code
When it comes to vUE code reuse, mixins come to mind. Mixins are something we both love and hate. First of all, mixins are really convenient. But the real situation is that a component often does not use all the functions in the Mixin it uses, but for the sake of convenience, sometimes people may choose to directly import Mixin simple and crude, but this also introduces many unnecessary functions for our component, and even a component introduces multiple mixins, resulting in the abuse of mixins. In addition, Mixin references are often imported implicitly. For example, when a function is called in the component, it is not known where the function comes from. Mixin problems cause confusion for subsequent project successors and increase the maintenance cost of the project.
The combined API can completely replace mixins. We can separate the common parts into a single function, which is hooks, which can import variables, functions, and so on into each component, as shown in the code above. Of course, mixins are supported in VUe3, but are not recommended in the official documentation. Vue3 project, Mixin should be able to use definitely do not use, use the combined API is not sweet.
Arguments to the setup function
The setup function takes two arguments
-
props
Similar to this.$props when using the option API, the props object contains only explicit declared prop, and all declared prop will appear in the props object, whether or not the parent component passed to it. The value of the optional prop that was not passed in will be undefined. Props is responsive, but cannot be deconstructed or it will be unresponsive.
-
Context:
Context is a context object that contains attributes attrs, slots, emit, and expose.
-
Attrs is equivalent to the familiar $attrs
-
Slots is the same as $slots
-
Emit is equivalent to $emit
-
Expose is a new function in Vue 3.2. It can only be called once, and you can specify the property of the component exposed to the component. The specified property can be used for the component instance obtained from ref, $parent or $root
<template> <div></div> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ name: 'ExposeComponent'.setup(props, { expose }){ function exposeExample(){ console.log('External methods')}function internalExample(){ console.log('Internal method')}// Expose exposeExample for external access expose({ exposeExample }) // Accessible from inside the component return { internalExample } }, }) </script> Copy the code
So the internalExample method is not accessible outside the component
-
Composite API lifecycle hooks
There are several types of lifecycle hooks
-
onBeforeMount
-
onMounted
-
onBeforeUpdate
-
onUpdated
-
onBeforeUnmount
-
onUnmounted
-
onActivated
-
onDeactivated
.
The life cycle hooks of the composite API are similar to the life cycle options of the optional API, except that the word “on” is added in front and the corresponding triggering logic is the same
The combined API lifecycle hooks do not require beforeCreate and Created, that is, there is no onBeforeCreate and onCreated, because setup executes before both. What needs to be done in beforeCreate and created can be done directly in setup().
import { onMounted, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() = > {
console.log('mounted')
})
onUnmounted(() = > {
console.log('unmounted')}}}Copy the code
Responsive API
One of the features of VUE is its bidirectional binding feature, which is convenient.
In VUe2, Object. DefineProperty is used to hijack data to achieve bidirectional binding
let definePropertyObj = {};
Object.defineProperty(definePropertyObj, 'prop', {
configurable: false.set: (v) = > {
definePropertyObj._v = v
},
get: () = > {
return definePropertyObj._v
}
});
Copy the code
As you can see, using Object.defineProperty to hijack data, since Vue performs getter/setter conversion during instance initialization, Vue needs to get the data’s property at the beginning. So that Vue can transform it and make it responsive
<template>
<div>
<div id='name'>{{ obj.name }}</div>
<div class="footer">
<button @click="changeName">Modify the name</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
obj:}}, {},methods: {
changeName() {
this.obj.name = 'the name value'}},}</script>
Copy the code
In the example above, the content of the “#name” element does not change after the button is clicked, unless the obJ in data originally had the name attribute
As a result, vue2 cannot detect property additions or deletions, and vue2 also provides vue.$set and vue.$delete apis to solve these problems
Vue3 implements “deep” responsive conversions to reference types based on ES2015 Proxy
let proxyObj = {};
let proxy = new Proxy(proxyObj, {
set: (obj, prop, value) = > {
proxyObj[prop] = value;
},
get: (obj, prop) = > {
return obj[prop]
},
deleteProperty: () = >{}});Copy the code
In this way, vue3 can add and delete Object attributes. Is vue3 abandoning Object.defineProperty and using proxy instead
Vue3 provides a variety of responsive apis to convert our variables.
reactive
import { reactive } from 'vue'
const state = reactive({value: 'vue'})
console.log(state)
Copy the code
We can find out by printing
Returns a Proxy object
In combination with the example of proxy mentioned above, we can find that proxy returns a reactive object of the original object, and Reactive returns a reactive copy of the object based on proxy. Therefore, the addition, removal and modification of OBJ mentioned above are all reactive, and the parameters of Reactive can only be reference types.
ref
Takes an internal value and returns a reactive and mutable REF object.
const value = ref('vue')
console.log(value)
Copy the code
We can find out by printing
Return a RefImpl object, let’s call it a REF object for short
The ref object leaks that there is one and only one value attribute pointing to that internal value.
import { ref } from 'vue'
const str = ref('vue2')
console.log(str.value) // vue2
str.value = 'vue3'
console.log(str.value) // vue3
Copy the code
When the ref object is returned from setup and accessed in the template, it automatically shallow unpackages the internal values, meaning that.value is not required. Only.value is added to the template when accessing nested refs
<template>
<div>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
<button @click="nested.count.value ++">Nested Increment count</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count,
nested: {
count
}
}
}
}
</script>
Copy the code
If the ref parameter is an object, the vUE automatically switches to reactive internally
const state = ref({
data: 'vue'
})
console.log(state)
console.log(state.value)
Copy the code
By printing in sequence we can see that the value of state.value is a Proxy object
Ref contrast reactive
Reactive returns a Proxy object that uses the Proxy for data hijacking
If the ref wrapped value is a primitive type (complex types are automatically converted to Reactive), data hijacking is performed using Object.defineProperty
So how does Proxy compare to object.defineProperty performance
By looking at the case of thecodebarbarian.com/thoughts-on…
const Benchmark = require('benchmark');
let suite = new Benchmark.Suite
let obj = {};
let definePropertyObj = {};
let proxyObj = {};
Object.defineProperty(definePropertyObj, 'prop', {
configurable: false.set: v= > definePropertyObj._v = v
});
let proxy = new Proxy(proxyObj, {
set: (obj, prop, value) = >{ proxyObj[prop] = value; }}); suite.add('normal'.function() {
obj.prop = 'gd';
})
.add('definePropertyObj'.function() {
definePropertyObj.prop = 'gd'
})
.add('proxyObj'.function() {
proxy.prop = 'gd'
})
.on('cycle'.function(event) {
console.log(String(event.target));
})
.on('complete'.function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ 'async': true });
Copy the code
/* Normal x 839,613,391 OPS/SEC ±1.93% (90 runs sampled) definePropertyObj x 846,333,142 OPS/SEC ±0.78% (91 runs (75 runs sampled) Fastest is definePropertyObj */
Copy the code
Object.defineproperty performs much better than proxy, and Vue3 uses proxy to listen for new and deleted Object attributes. Therefore, ref is recommended to be used in practice. In vue3.2, ref has been officially optimized to increase the reading speed by about 260% and the writing speed by about 50%.
By the way, when vue2 makes a reactive conversion to an Object, it uses object.defineProperty to convert a child Object into a reactive Object through a recursive Object, in the case of an Object’s property or Object. Vue3, on the other hand, is lazy, turning the object into responsive only when its properties are accessed. This is also a performance optimization for VUE3.
isRef
Check if the value is a ref object.
import { ref, isRef } from 'vue'
const str = 'vue2'
const refStr = ref('vue3')
console.log(isRef(str)) // false
console.log(isRef(refStr)) // true
Copy the code
unref
If the argument is a ref object, the internal value is returned, otherwise the argument itself is returned.
import { ref, unref } from 'vue'
const str = 'vue2'
const refStr = ref('vue3')
console.log(unref(str)) // 'vue2'
console.log(unref(refStr)) // 'vue3'
Copy the code
toRef
Create a ref object for a property of a responsive object. The ref can be passed, and it retains a reactive connection to its source property
const states = reactive({
num: 0});const numRef = toRef(states, "num");
console.log(states.num); / / 0
console.log(numRef.value, isRef(numRef)); // 0 true
numRef.value++;
console.log(numRef.value); / / 1
console.log(states.num); / / 1
states.num++;
console.log(numRef.value); / / 2
console.log(states.num); / / 2
Copy the code
What if toRef is used for normal objects
const obj = {
str: 'vue'
}
const strRef = toRef(obj, 'str')
console.log(strRef.value) // vue
function changeStrRef(){
strRef.value = 'vue3'
console.log(obj) // { str: "vue3" }
console.log(strRef.value) // vue3
}
function changeObjStr(){
obj.str = 'vue2'
console.log(obj) // { str: "vue2" }
console.log(strRef.value) // vue2
}
Copy the code
As you can see, normal objects can be created, but the page does not respond
toRefs
As the name implies, the complex version of toRef above creates a ref object for all properties of a responsive object
const states = reactive({
num: 0.figure: 1});const { num, figure } = toRefs(states); // Structuring the value returned by toRefs does not affect its responsiveness
console.log(states.num); / / 0
console.log(states.figure); / / 1
console.log(num.value); / / 0
console.log(figure.value); / / 1
states.num++;
console.log(num.value); / / 1
figure.value++;
console.log(states.figure); / / 2
Copy the code
Combining Reactive and toRefs makes it easy to reference reactive data in a template
<template>
<div>
<div>Name: {{name}}</div>
<div>Gender: {{sex}}</div>
<div>Age: {{age}}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "responsive".setup() {
const state = reactive({
name: 'Paul'.sex: 'male'.age: 18
})
return {
...toRefs(state)
}
},
});
</script>
Copy the code
So if you have any questions here, setup is straightforward
return {
...state
}
Copy the code
Not line?
Of course not. The way it’s written is equivalent to
return {
name: state.name,
sex: state.sex,
age: state.age,
}
Copy the code
It’s like exposing simple strings and values, so there’s no responsiveness, that is, when properties in state change, the view data doesn’t change.
readonly
Readonly can return a read-only proxy for an object, and is read-only deep (as opposed to const).
Readonly is an object, which can be either reactive or normal, or a REF object.
<script lang="ts">
import { defineComponent, readonly, onMounted } from "vue";
export default defineComponent({
name: "readonly".setup() {
const readonlyObj = readonly({
value: 'apple'
});
onMounted(() = > {
readonlyObj.value = 'banner';
console.log(readonlyObj.value); // apple}); }});</script>
Copy the code
The above code will report the following error
IsProxy, isReactive, and isReadonly
-
IsProxy does not check whether the object is a new Proxy object, but whether the object is a Proxy created by Reactive or Readonly. Boolean is returned
const state = reactive({ num: 0 }) const readonlyObj = readonly({ num: 0 }); const refObj = ref({ num: 0 }) const numRef = ref(0) let proxyObj = Object.create(null); let newProxy = new Proxy(proxyObj, {}); console.log(isProxy(state)) // true console.log(isProxy(readonlyObj)) // true console.log(isProxy(numRef)) // false console.log(isProxy(refObj)) // false console.log(isProxy(refObj.value)) / / true / / here you can verify vue3 ref parameters are objects, automatic call reactive console.log(isProxy(newProxy)) // false Copy the code
-
isReactive
Check whether the object is a reactive agent created by Reactive.
const state = reactive({ num: 0 }) const readonlyObj = readonly({ num: 0 }); const readonlyState = reactive(state) const refObj = ref({ num: 0 }) console.log(isReactive(state)) // true console.log(isReactive(readonlyObj)) // false console.log(isReactive(refObj)) // false console.log(isReactive(refObj.value)) // true console.log(isReactive(readonlyState)) // true Copy the code
As you can see from the code above, if the readOnly parameter is a return object created by Reactive, it can also pass the isReactive check
-
isReadonly
Check whether the object is created by readonly
const readonlyObj = readonly({ num: 0 }); console.log(isReadonly(readonlyObj)) // true Copy the code
toRaw
Returns the original object of the Reactive or Readonly agent. As we all know, Proxy is a Proxy object created based on the original object, reactive and Readonly are implemented based on the Proxy, and toRaw can return its original object
const obj = {}
const reactiveObj = reactive(obj)
console.log(toRaw(reactiveObj) === obj) // true
Copy the code
Computed
Computed by default receives a getter function that returns an unmodifiable responsive ref object.
A computed parameter can also be an object with get and set functions that return a ref object that can be modified.
Note that computed returns a REF object, so you need to add.value when accessing the value of a modified computed attribute in setup.
<template>
<div>
<div>
{{ num }}
</div>
<div>
{{ result }}
</div>
<div>
{{ setResult }}
</div>
<el-button @click="addNum">Num 1</el-button>
<el-button @click="setNum">Set the setResult</el-button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from "vue";
export default defineComponent({
name: "computed".setup() {
let num = ref<number>(0);
const result = computed(() = >num.value+1)
const setResult = computed({
get: () = > num.value+2.set: (val) = > num.value = num.value - val
})
function setNum (){
setResult.value = 1
}
function addNum(){
num.value++
}
return{ num, result, addNum, setResult, setNum, }; }});</script>
Copy the code
Watch
This.$watch is the same as this
import { watch } from vue
Copy the code
Watch can accept three parameters,
The first argument is the listening source, which can be a getter, a ref, a response object, or an array of these types. The second argument is the callback function that contains both the old and new arguments of the listening source. The third argument is options, the familiar deep and immediate arguments
Single data source
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
export default defineComponent({
name: "watch".setup() {
let num = ref<number>(0);
watch(num, (newVal, oldVal) = > {
console.log('Listen to change (watch: num)', newVal, oldVal)
}
)
watch(() = >num.value, (newVal, oldVal) = > {
console.log('Watch: ()=>num.value', newVal, oldVal)
}
)
num.value++ (watch: num) 1 0
(watch: ()=>num.value) 1 0
return{ num, }; }});</script>
Copy the code
After executing num++, you can see that the printed values are actually the same
Watch to monitor reactive
<script lang="ts">
import { defineComponent, ref, watch } from "vue";
export default defineComponent({
name: "watch".setup() {
const state = reactive({
name: "Jack"}); watch( state,(newValues, prevValues) = > {
console.log(newValues, prevValues);
console.log(newValues.name, prevValues.name); }); state.name ="Mike"; }});</script>
Copy the code
In the example above, if we listen on state directly, we print out that newValues, prevValues are the same,
At this point we can modify the above code
watch(
() = > state.name,
(newValues, prevValues) = > {
console.log(newValues, prevValues); });Copy the code
At this point, we can find that the effect we want to come out.
Multiple data sources
Watch can also listen for multiple data by passing arrays
let surnames = ref("");
let name = ref("");
watch([surnames, name], (newValues, prevValues) = > {
console.log(newValues, prevValues);
});
surnames.value = "Michael"; // logs: ["Michael", ""] ["", ""]
name.value = "Jackson"; // logs: ["Michael", "Jackson"] ["Michael", ""]
Copy the code
watchEffect
Vue3’s new function, officially described as: executes a function passed in immediately, traces its dependencies in a responsive manner, and rerun the function when its dependencies change.
Let’s look at an example
<template>
<div>
<el-button @click="changeAppleAmount">Apple plus one</el-button>
<el-button @click="changeBannerAmount">Add a banana</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from "vue";
export default defineComponent({
name: "watchEffect".setup() {
let appleAmount = ref(0);
let bannerAmount = ref(0);
watchEffect(() = >
console.log(
'Altogether apples${appleAmount.value}B: Yes, bananas altogether${bannerAmount.value}A `));function changeAppleAmount() {
appleAmount.value++;
}
function changeBannerAmount() {
bannerAmount.value++;
}
return{ changeAppleAmount, changeBannerAmount, }; }});</script>
Copy the code
You can see that the watchEffect function is executed once at first, and then again when the value involved in the watchEffect function changes.
So this function is kind of sandwiched between Computed and Watch. It doesn’t need to return a value like Computed, and it doesn’t need to explicitly write the source of the monitor like Watch, and it can’t get the old and new values before and after the changed values.
Watch and watchEffect stop listening
Watch and watchEffect stop listening when the component is unloaded, but you can manually stop listening by calling its return value
<template>
<el-card>
<el-button @click="quantity++">The number one</el-button>
<el-button @click="stopWatchEffect">Stop watchEffect</el-button>
<el-button @click="stopWatch">Stop watch</el-button>
</el-card>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, watch } from "vue";
export default defineComponent({
name: "watchEffect".setup() {
const quantity = ref(0);
const watchEffectStop = watchEffect(() = >
console.log(`watchEffect: ${quantity.value}`));const watchStop = watch(quantity, (newValue, oldValue) = >
console.log(`watch: ${newValue}`));function stopWatchEffect() {
watchEffectStop(); // Stop watchEffect listening
}
function stopWatch() {
watchStop(); // Stop the watch
}
return{ quantity, stopWatchEffect, stopWatch, }; }});</script>
Copy the code
Template reference (ref)
In the optional API we can get the specified element by ref
<template>
<div class="test" ref="test"></div>
</template>
<script>
export default {
mounted() {
let test = this.$refs.test
console.log(test)
},
}
</script>
Copy the code
Take a look at a small example of using ref in a composite API
<template>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="rules"
label-width="120px"
class="demo-ruleForm"
>
<el-form-item label="Name" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="submitForm()">Create</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { ElForm } from "element-plus";
export default defineComponent({
setup() {
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
const rules = {
name: [{ required: true}]};const ruleForm = ref({
name: ""});function submitForm() { ruleFormRef.value? .validate((valid) = > {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false; }}); }return{ ruleForm, ruleFormRef, rules, submitForm, }; }});</script>
Copy the code
Here the ruleFormRef we returned in setup leaks out and applies it to the value of div’s ref attribute. Note that ref cannot be preceded by a colon, because if it is, the value of ref is empty, otherwise it will be “ruleFormRef” as a string. In vue3’s virtual DOM patching algorithm, if the REF key of a VNode corresponds to the REF in the rendering context, vue3 will assign the corresponding element or component instance of the VNode to the value of the REF. This is performed during the virtual DOM mounting/patching process. So template references only get assigned after the initial rendering.
The Refs used as templates behave like any other ref: they are reactive and can be passed into (or returned from) composite functions.
route router
When using a route Router in a composite API, you can use useRoute, useRouter
UseRoute () and useRouter() use the same route and router as routerOute RouterOuter
<script lang="ts">
import { defineComponent, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export default defineComponent({
setup() {
const route = useRoute()
const router = useRouter()
watch(
() = > route.path,
() = >{... }, {immediate: true})function goPath() {
router.push(path)
}
}
})
</script>
Copy the code
vuex
When using store in the composite API, you can also use useStore. UseStore () returns the same store as vue2’s $store
<script lang="ts">
import { defineComponent } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const companyInfo = computed(() = > store.state.user.companyInfo)
return {
companyInfo,
}
},
})
</script>
Copy the code
instruction
Custom instruction
Vue2’s custom instruction hook function
- Bind: Called only once, the first time a directive is bound to an element.
- 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: invoked when the VNode where the component resides is updated.
- 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.
Vue3 updates the custom instruction hook function
- Created: called before the attribute or event listener of the bound element is applied.
- BeforeMount: Called when the directive is first bound to an element and before the parent component is mounted. (Same as bind for vue2)
- Mounted: Is invoked after the parent component of the bound element is mounted. (Same as vue2)
- BeforeUpdate: Called before updating a VNode that contains components.
- Updated: Called after the VNode that contains the component and its children are updated. (Same as VUe2’s componentUpdated)
- BeforeUnmount: Called before unmounting the parent component of a bound element.
- Unmounted: This command is invoked only once when it is unbound from an element and the parent component is unmounted. (same as unbind for vue2)
It can be found that vue3 extends more hooks on the basis of VUe2, enriches the application scenarios of instructions, and makes naming easier to understand.
Vue3’s hook functions have the same parameters as before (i.e. El, binding, vNode, and prevVnode)
Other updates
Multi-node component support
Vue2 requires a root component in its template, whereas vue3 components can contain multiple root nodes. However, we need to define which root component the attribute should be distributed to. Otherwise, we need to refer to the component. Defining a class on a component tag does not know which root node the class is defined on
<template>
<p class='header'>.</p>
<div v-bind="$attrs">.</div>
<div>.</div>
</template>
Copy the code
Note: But vue directives, unlike attributes, are not passed into elements via $attrs. When they are used with multi-root components, they are ignored and vue throws warnings
Teleport
React Portal is a tag that allows you to control which parent node elements are rendered under
<template>
<div>
<div>In the component</div>
<teleport to='body'>
<div class="teleport">Embedded in the body</div>
</teleport>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
})
</script>
Copy the code
Define custom events emits
We are already familiar with the custom events of VUE. In Vue3, custom events need to be defined in the emits option. If they are not defined, they can be executed, but VUE will issue a warning.
And if emits defines native events, they are replaced by events in the component.
In vue2, we can bind native events to the root element of a child component using the. Native modifier, whereas vue3 has removed the V-on. native modifier. We can leave the corresponding native event undefined on the emits option, and the native event is bound to the root element by default
/ / the parent component<template>
<children @focus="parentsEvent" />
</template>
<script lang="ts">
import { defineComponent } from "vue";
import children from "./components/children.vue";
export default defineComponent({
components: { children },
setup() {
function parentsEvent() {
console.log("Child element focus");
}
return{ parentsEvent, }; }});</script>/ / child component<template>
<input type="text" />
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "children".emits: [].emits: ["focus"] is not defined here, so the parent component's parentsEvent event is triggered when the child's input element is focused
});
</script>
Copy the code
Remove filter
In fact, filter can be replaced by methods or computed
Removed. The sync
Normally, we cannot modify the received prop value. In VUe2, we can use the.sync modifier to “bidirectional bind” a prop.
/ / the parent component<template>
<div>
<myDialog :dialogVisible.sync="visible" />
<el-button @click="showDialog">Open the popup window</el-button>
</div>
</template>
<script>
import myDialog from './components/myDialog.vue'
export default {
components: { myDialog },
data() {
return {
visible: false,}},methods: {
showDialog() {
this.visible = true}},}</script>/ / child component<template>
<el-dialog
title="Tip"
:visible.sync="dialogVisible"
width="30%"
:before-close="close"
>
<el-button type="primary" @click="close">Shut down</el-button>
</el-dialog>
</template>
<script>
export default {
props: {
dialogVisible: {
type: Boolean.default: false,}},methods: {
close() {
this.$emit('update:dialogVisible'.false)}}},</script>
Copy the code
In vue3,.sync has been removed because vue3 changes the use of v-model on the component and no longer requires.sync
<MyComponent v-model="componentVisible" />/ / equivalent to<MyComponent :modelValue="componentVisible" @update:modelValue="componentVisible = $event"/>
Copy the code
V-model can also add parameters
<MyComponent v-model:visible="componentVisible" />/ / equivalent to<MyComponent :componentVisible="componentVisible" @update:componentVisible="componentVisible = $event"/>
Copy the code
Example:
/ / the parent component<template>
<div>
<MyDialog v-model:dialogVisible="visible" />
<el-button @click="open">Open the popup window</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import MyDialog from "./components/myDialog.vue";
export default defineComponent({
components: { MyDialog },
setup() {
const visible = ref(false);
function open() {
visible.value = true;
}
return{ visible, open, }; }});</script>// Bullet window assembly<template>
<el-dialog
:model-value="dialogVisible"
title="Tips"
width="30%"
:before-close="close"
>
<span>This is a message</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">cancel</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "MyDialog".props: {
dialogVisible: {
type: Boolean.default: false,}},emits: ["update:dialogVisible"].setup(props, { emit }) {
function close() {
emit("update:dialogVisible".false);
}
return{ close, }; }});</script>
Copy the code
Also, you can have multiple V-Models on a component
<MyComponent v-model:visible="componentVisible" v-model:userId="dataUserId" />
Copy the code
Remove $listeners
$Listeners have been removed from Vue 3 and the event listeners have been merged with $attrs
/ / the parent component<template>
<children @parentsEvent="parentsEvent" :count="count" />
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import children from "./components/children.vue";
export default defineComponent({
name: "listenersAttrs".components: { children },
setup() {
const count = ref(0);
function parentsEvent(value: string) {
console.log(value);
}
return{ count, parentsEvent, }; }});</script>/ / child component<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent, onMounted } from "vue";
export default defineComponent({
name: "children".setup(props, { attrs }) {
onMounted(() = > {
console.log(attrs); }); }});</script>
Copy the code
The print result is as follows
Remove $on, $off, and $once
In vue2, we can create a new Vue() instance, and then perform $on, $off, and $once operations on this instance to achieve cross-component communication, namely the so-called Vue bus, but the new Vue() is no longer available. $on, $off, and $once have also been removed. After all, too much bus will make the project look too messy. Use vuex or something else instead.
vue3.2
<script setup>
In <script setup> we do not need to declare export default and setup methods. This way we can expose the top-level bindings of all < Script setup> declarations, including variables, functions, and import contents, and use them directly in the template. Look at the sample
<template>
<div>
{{ data }}
<MyComponents/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MyComponents from './components/MyComponents.vue';
const data = 'vue3.2'
</script>
Copy the code
DefineProps, defineEmits
Because there is no setup function in <script Setup > and no props, emits option, component communication using props and emits in this way requires defineProps and defineEmits.
DefineProps and defineEmits are only available in <script Setup > and don’t need to be imported from “vue” or anywhere else.
DefineProps receives the same value as the props option, and defineEmits receives the same value as the emits option.
/ / the parent component<template>
<div>
<MyComponents :propsCount='propsCount' @changePropsCount='changePropsCount'/>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MyComponents from './components/MyComponents.vue';
const propsCount = ref(1)
function changePropsCount(){
propsCount.value++
}
</script>/ / child component<template>
<div>
<p>{{ propsCount }}</p>
<el-button @click="emit('changePropsCount')">increase</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const props = defineProps({
propsCount: Number});const emit = defineEmits(['changePropsCount'])
</script>
Copy the code
defineExpose
As usual with setup, the second argument to the setup function has an expose method property if the component needs to expose properties for external use
DefineExpose is available in < Script Setup > and is used the same way as Expose
<script setup>
import { ref } from 'vue'
const outData = ref(1)
defineExpose({
outData
})
</script>
Copy the code
useSlots useAttrs
Again, in the normal setup function, the second argument to the setup function has the attrs and slots attributes
In <script Setup > you can use useSlots useAttrs
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>// The use of slots and attrs is the same as beforeCopy the code
Used with plain <script>
You may find that <script Setup > is written as if it doesn’t define the component name name
We can use it with normal <script>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ComponentName',})</script>
<script setup>.</script>
Copy the code
v-bind
An impressive new feature is the ability to use variables of the current component in the style tag of a single file via V-bind, as shown below
<template>
<div class="box"></div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "css-v-bind".setup() {
let width = ref("100px");
let height = ref("100px");
let background = ref("#00cd96");
return{ width, height, background, }; }});</script>
<style scoped>
.box {
width: v-bind(width);
height: v-bind(height);
background: v-bind(background);
}
</style>
Copy the code
v-memo
The V-Memo directive, an instruction to avoid invalid duplicate renderings, takes an array of dependencies (arrays can have multiple dependencies) and determines if the render needs to be updated based on the values in the array.
<template>
<div>
<div v-memo="[divMemoValue]">
{{ innerData }}
</div>
<button @click="changeDivData">Modify div data</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const divMemoValue = ref(1);
const innerData = ref("Box data 1");
function changeDivData() {
innerData.value = "Box data 2";
}
return{ divMemoValue, innerData, changeDivData, }; }});</script>
Copy the code
As you can see in the example above, clicking the button to modify innerData does not change the view, because the divMemoValue in the V-Memo is not changed, so it does not trigger dom re-rendering.
According to this principle, we can reduce re-rendering appropriately and improve performance according to our actual development projects.
If the VALUE of the V-Memo is an empty array, then the corresponding element or component will only be rendered once and will not be updated, as with v-once
This is especially true in combination with V-for
Used in combination with V-for
Look at the following example
<template>
<div>
<div
v-for="item in list"
:key="item.order"
v-memo="[item.order === activeOrder]"
>{{ item.order === activeOrder ? "Checked" : "not checked"}}<button @click="selectItem(item.order)">choose</button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const list = ref([
{
order: 1.label: "Data 1"}, {order: 2.label: "The data 2"}, {order: 3.label: "Data 3"}, {order: 4.label: 4 "data",}]);const activeOrder = ref(1);
function selectItem(order: number) {
activeOrder.value = order;
}
return{ activeOrder, list, selectItem, }; }});</script>
Copy the code
In the above example, when we update the selected list data, only the “item.order === activeOrder” condition of the previously selected and newly selected data will change. The “item.order === activeOrder” condition of the other two data will remain false. So the other two data updates were skipped to optimize performance.
Of course, this is only a simple example. In real development, there may be hundreds or thousands of data pieces, and the DOM rendered by each data piece may be more complex, so the performance of the actual optimization will be particularly significant.