Vue3.0 has been updated and optimized since the first One Piece version was released in September, 20. Official documentation in Chinese has also been released; As end users, let’s take a look at what Vue3 has added.

This article was first published in the public number [front-end one read], more exciting content please pay attention to the latest news of the public number.

During his live broadcast at Station B, You shared a few highlights of Vue3.0:

  • “Performance” : Performance optimization
  • Tree-shaking Support: Supports Tree shaking
  • Composition API: Composition API
  • Fragments, Teleport, Suspense: New components
  • Better TypeScript support: Better TypeScript support
  • Custom Renderer API: Custom Renderer

In terms of performance, compared with Vue2. X, the performance is improved by about 1.3~2 times. The size of the package is also smaller, if only a HelloWorld package, only 13.5 KB; With all the runtime features, it’s just 22.5 KB.

So how do we, as end users, differ from Vue2. X in development? Talk is cheap, let’s look at the code.

Tree-shaking

One of the most important changes to Vue3 is the introduction of tree-shaking, and the smaller size of the bundle that tree-shaking brings is obvious. In the 2.x version, many functions are mounted on the global Vue object, such as nextTick, nextTick, nextTick, set, etc., so although we may not need them, they are still packaged into the bundle once the Vue is introduced.

In Vue3, all apis are introduced in ES6 modularity, which allows packaging tools such as WebPack or Rollup to eliminate unused apis and minimize bundle volume. We can see this change in main.js:

//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

const app = createApp(App);
app.use(router).mount("#app");
Copy the code

CreateApp instance from new Vue() to createApp; But some core features like virtualDOM update algorithms and responsive systems will be packaged anyway; The change is that components (Vue.component), directives (vue.directive), mixin (vue.mixin) and plug-ins (vue.use) that were previously configured globally are now mounted directly on instances; We use the created instance to call, which brings the advantage that an application can have multiple Vue instances, and the configuration between different instances does not affect each other:

const app = createApp(App)
app.use(/ *... * /)
app.mixin(/ *... * /)
app.component(/ *... * /)
app.directive(/ *... * /)
Copy the code

Therefore, the following global apis for vue2.x also need to be changed to ES6 modular introduction:

  • Vue.nextTick
  • Ue. Observable is no longer supportedreactive
  • Vue.version
  • Vue.compile (full build only)
  • Vue.set (build only)
  • Vue.delete (build only)

Vuex and VuE-Router are also improved using tree-shaking, but the SYNTAX of the API is similar:

//src/store/index.js
import { createStore } from "vuex";

export default createStore({
  state: {},
  mutations: {},
  actions: {},
  modules: {},});//src/router/index.js
import { createRouter, createWebHistory } from "vue-router";

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});
Copy the code

More on the use of tree-shaking can be found in the Webpack configuration full resolution.

Life cycle function

As we all know, there are eight lifecycle functions in Vue2. X:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

In vue3, a new setup life cycle function is added. The setup life is executed before the beforeCreate life function, so this cannot be used to fetch instances in this function. BeforeDestroy is renamed beforeUnmount and destroyed is renamed unmounted for naming purposes. Therefore, vue3 has the following lifecycle functions:

  • BeforeCreate (setup is recommended instead)
  • Created (Setup is recommended instead)
  • setup
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

Vue3 also has a new lifecycle hook. We can access the lifecycle of a component by adding on before the lifecycle function. We can use the following lifecycle hooks:

  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
  • onErrorCaptured
  • onRenderTracked
  • onRenderTriggered

So how do these hook functions get called? We mount the lifecycle hook in setup and call the corresponding hook function when the lifecycle is executed:

import { onBeforeMount, onMounted } from "vue";
export default {
  setup() {
    console.log("----setup----");
    onBeforeMount(() = > {
      // beforeMount code execution
    });
    onMounted(() = > {
      // mounted}); }},Copy the code

New features

With the lifecycle out of the way, here’s what we can expect to see added to Vue3.

Responsive API

We have studied Object defineProperty and Proxy in depth and explained the advantages of Proxy and why Vue3 uses Proxy to implement responsiveness. Meanwhile, Vue3 also removes some responsive apis to make the code more reusable.

We can use Reactive to create reactive state for JS objects:

import { reactive, toRefs } from "vue";
const user = reactive({
  name: 'Vue2'.age: 18}); user.name ='Vue3'
Copy the code

Reactive is equivalent to vue.Observable in Vue2. X.

Reactive only accepts complex data types such as Object and array.

For some basic data types, such as strings and values, we want to make them reactive. We can also create objects using reactive functions, but Vue3 provides another function called ref:


import { ref } from "vue";
const num = ref(0);
const str = ref("");
const male = ref(true);

num.value++;
console.log(num.value);

str.value = "new val";
console.log(str.value);

male.value = false;
console.log(male.value);
Copy the code

The RefImpl object returned by ref is a RefImpl object containing only one parameter named value, which is obtained and modified in JS through its value property. However, when rendered in the template, the internal values are automatically expanded, so there is no need to append.value to the template.

<template> <div> <span>{{ count }}</span> <button @click="count ++">Increment count</button> </div> </template> <script>  import { ref } from 'vue' export default { setup() { const count = ref(0) return { count } } } </script>Copy the code

Reactive deals with complex data structures, whereas REF deals with basic data structures. A ref can only handle basic data, but it can also handle objects and arrays:

import { ref } from "vue";

const obj = ref({
  name: "qwe".age: 1});setTimeout(() = > {
  obj.value.name = "asd";
}, 1000);

const list = ref([1.2.3.4.6]);
setTimeout(() = > {
  list.value.push(7);
}, 2000);
Copy the code

When dealing with properties of large responsive objects, we want to use ES6’s deconstruction to get the values we want:

let book = reactive({
  name: 'Learn Vue'.year: 2020.title: 'Chapter one'
})
let {
  name,
} = book

name = 'new Learn'
// Learn Vue
console.log(book.name);
Copy the code

But unfortunately, that gets rid of its responsiveness; In this case, we can convert reactive objects to a set of Refs that retain their reactive associations with the source objects:

let book = reactive({
  name: 'Learn Vue'.year: 2020.title: 'Chapter one'
})
let {
  name,
} = toRefs(book)

// Note that name is the ref object
// Assign values by value
name.value = 'new Learn'
// new Learn
console.log(book.name);
Copy the code

For some read-only data, we want to prevent it from changing at all. We can create a read-only object by readonly:

import { reactive, readonly } from "vue";
let book = reactive({
  name: 'Learn Vue'.year: 2020.title: 'Chapter one'
})

const copy = readonly(book);
//Set operation on key "name" failed: target is readonly.
copy.name = "new copy";
Copy the code

Sometimes we need values that depend on the state of other values. In vue2. X we use computed functions to calculate properties.

const num = ref(0);
const double = computed(() = > num.value * 2);
num.value++;
/ / 2
console.log(double.value);
// Warning: computed value is readonly
double.value = 4
Copy the code

Alternatively, we can create a ref object that can be read and written using the get and set functions:

const num = ref(0);
const double = computed({
  get: () = > num.value * 2.set: (val) = > (num.value = val / 2)}); num.value++;/ / 2
console.log(double.value);

double.value = 8
/ / 4
console.log(num.value);
Copy the code

Responsive interception

The opposite of computed is Watch. Computed is many-to-one, and watch is one-to-many. Vue3 also provides two functions to listen for changes to the data source: Watch and watchEffect.

Let’s look at watch, which uses exactly the same way as the watch option of the component. It needs to listen on a data source and then execute the specific callback function. Let’s look at how it listens on a single data source:

import { reactive, ref, watch } from "vue";

const state = reactive({
  count: 0});// Return the worth getter while listening
watch(
  () = > state.count,
  (count, prevCount) = > {
    / / 1 0
    console.log(count, prevCount); }); state.count++;const count = ref(0);
// listen on ref directly
watch(count, (count, prevCount) = > {
  / / 2 0
  console.log(count, prevCount, "watch");
});
count.value = 2;
Copy the code

We can also listen for multiple values in an array and return the final value as an array:

const state = reactive({
  count: 1});const count = ref(2);
watch([() = > state.count, count], (newVal, oldVal) = > {
  / / [3, 2] [1, 2]
  / / [3, 4] [3, 2)
  console.log(newVal, oldVal);
});
state.count = 3;

count.value = 4;
Copy the code

If we are listening for a deeply nested object property change, we need to set deep:true:

const deepObj = reactive({
  a: {
    b: {
      c: "hello",}}}); watch(() = > deepObj,
  (val, old) = > {
    // new hello new hello
    console.log(val.a.b.c, old.a.b.c);
  },
  { deep: true}); deepObj.a.b.c ="new hello";
Copy the code

This is because listening for a responsive object always returns a reference to that object, so we need to make a deep copy of the value:

import _ from "lodash";
const deepObj = reactive({
  a: {
    b: {
      c: "hello",}}}); watch(() = > _.cloneDeep(deepObj),
  (val, old) = > {
    // new hello hello
    console.log(val.a.b.c, old.a.b.c);
  },
  { deep: true}); deepObj.a.b.c ="new hello";
Copy the code

Normally, listening stops automatically when the component is destroyed, but sometimes we want to stop the component manually before it is destroyed by calling the stop function returned by Watch:

const count = ref(0);

const stop = watch(count, (count, prevCount) = > {
  / / does not perform
  console.log(count, prevCount);
});

setTimeout(() = >{
  count.value = 2;
}, 1000);
/ / stop watch
stop();
Copy the code

There is also a watchEffect function that can be used to listen, but there is already a Watch. What is the difference between watchEffect and Watch? There are several main differences in their usage:

  1. WatchEffect does not require manually passing in dependencies
  2. WatchEffect executes a callback every time it is initialized to get the dependency automatically
  3. WatchEffect does not get the original value, only the changed value
import { reactive, ref, watch, watchEffect } from "vue";

const count = ref(0);
const state = reactive({
  year: 2021}); watchEffect(() = > {
  console.log(count.value);
  console.log(state.year);
});
setInterval(() = > {
  count.value++;
  state.year++;
}, 1000);
Copy the code

WatchEffect executes automatically once a page loads to track responsive dependencies; When the timer is executed every 1s after loading, the watchEffect will automatically execute after monitoring the change of data, and each execution will obtain the changed value.

Combination of the API

Composition API is one of the most important features in Vue3. The previous version of 2.x used the Options API, which is officially defined: The problem with data, computed, and methods is that code gets more complex as it has more functions, and it needs to bounce up and down repeatedly:

In the figure above, one color represents a function. We can see that the Options API function code is scattered. The Composition API organizes the logic of the same function into a single function for easy maintenance.

Let’s look at the syntax of the Options API:

export default {
  components: {},
  data() {},
  computed: {},
  watch: {},
  mounted(){},}Copy the code

Options API is to put the same type of things in the same option, when we have less data, such organization is relatively clear; However, with the increase of data, the function points we maintain will involve multiple data and methods, but we cannot perceive which data and methods need to be involved, so we often need to switch back and forth to find, or even understand the logic of other functions, which also leads to the difficulty of understanding and reading components.

What the Composition API does is to keep the code of the same function together, so that when you need to maintain a function point, you don’t have to worry about other logic, just focus on the current function. The Composition API organizes code with setup options:

export default {
  setup(props, context){}};Copy the code

Here we see that it takes two parameters: props and context. The props is some data passed in by the parent component. Context is a context object, and some properties are exposed from 2.x:

  • attrs
  • slots
  • emit

Note: The props data also needs to be deconstructed through toRefs, otherwise the reactive data will be invalid.

Let’s use a Button to see how setup works:

<template> <div>{{ state.count }} * 2 = {{ double }}</div> <div>{{ num }}</div> <div @click="add">Add</div> </template> <script> import { reactive, computed, ref } from "vue"; export default { name: "Button", setup() { const state = reactive({ count: 1, }); const num = ref(2); function add() { state.count++; num.value += 10; } const double = computed(() => state.count * 2); return { state, double, num, add, }; }}; </script>Copy the code

Many children may have doubts, which is no different from what I wrote in data and methods, just put them together. Setup functions can be extracted and divided into separate functions, each of which can be logically reused in different components:

export default {
  setup() {
    const { networkState } = useNetworkState();
    const { user } = userDeatil();
    const { list } = tableData();
    return{ networkState, user, list, }; }};function useNetworkState() {}
function userDeatil() {}
function tableData() {}
Copy the code

Fragment

A Fragment is a Fragment. In ve2. X, it is required that each template must have a root node, so our code reads like this:

<template>
  <div>
    <span></span>
    <span></span>
  </div>
</template>
Copy the code

Or in Vue2.x you can introduce a vue-Fragments library that replaces div with a virtual fragment; In React, the solution is to create a virtual element with a react. Fragment tag; In Vue3 we can do without the root node:

<template>
    <span>hello</span>
    <span>world</span>
</template>
Copy the code

There are fewer div elements that don’t make sense.

Teleport

Teleport translates to send, Teleport; As the name suggests, it can transfer elements or components from a slot to another location on the page:

In React, you can use the createPortal function to create nodes that need to be transferred. Originally uVU wanted to call it Portal, but the Portal TAB native to H5 was also planned, although there were some security issues, it was changed to Teleport to avoid the same name.

A common use of Teleport is to shift the position of a modal box in some deeply nested components. Although the modal box is logically part of the component, in terms of style and DOM structure, the nesting level is deep and not easy to maintain (z-index issues, etc.); So we need to separate it out:

<template> < button@click ="showDialog = true"> </button> <teleport to="body"> <div class="modal" V-if ="showDialog" style="position: </button> <child-component: MSG =" MSG "></child-component> </div> </teleport> </template> <script> export default { data() { return { showDialog: false, msg: "hello" }; }}; </script>Copy the code

The Modal div in the Teleport here is passed to the bottom of the body; Although rendered in different places, the elements and components in the Teleport are still logical children of the parent and can communicate with the parent. Teleport accepts two parameters to and disabled:

  • To-string: must be a valid query selector or HTMLElement, id or class selector, etc.
  • Disabled-boolean: True disables teleport. Teleport contents will not be moved to any location. Default: False disables teleport.

Suspense

Suspense is a built-in component from Vue3 that allows our applications to render back-up content while waiting for asynchronous components, allowing us to create a smooth user experience; Vue has an asynchronous component loaded in Vue2. X. The route component loaded in vuue -router is also an asynchronous component:

export default {
  name: "Home".components: {
    AsyncButton: () = > import(".. /components/AsyncButton"),}}Copy the code

Redefined in Vue3, asynchronous components need to be explicitly defined via defineAsyncComponent:

// Define asynchronous components globally
//src/main.js
import { defineAsyncComponent } from "vue";
const AsyncButton = defineAsyncComponent(() = >
  import("./components/AsyncButton.vue")); app.component("AsyncButton", AsyncButton);


// Define asynchronous components within the component
// src/views/Home.vue
import { defineAsyncComponent } from "vue";
export default {
  components: {
    AsyncButton: defineAsyncComponent(() = >
      import(".. /components/AsyncButton")),}};Copy the code

At the same time, asynchronous components can be managed more carefully:

export default {
  components: {
    AsyncButton: defineAsyncComponent({
      delay: 100.timeout: 3000.loader: () = > import(".. /components/AsyncButton"),
      errorComponent: ErrorComponent,
      onError(error, retry, fail, attempts) {
        if (attempts <= 3) {
          retry();
        } else{ fail(); }},}),},};Copy the code

This allows us to keep track of asynchronous component loading and reload or display exception status in case of load failure:

Suspense goes back to Suspense. It mainly renders back-up content at component load time and provides two slot slots, one for default and one for fallback state:

< girl > < girl > < girl > < girl > < girl > < girl > < girl > < girl > < girl > < girl > < girl </AsyncButton> </template> <template #fallback> <div> </div> </template> </Suspense> </template> </div> </template> <script> export default { setup() { const isShowButton = ref(false); function showButton() { isShowButton.value = true; } return { isShowButton, showButton, }; }, } </script>Copy the code

Incompatible functions

The non-compatible features are mainly syntax changes from the Vue2. X version, which may have compatibility issues in Vue3.

Data, mixin, and filter

In Vue2. X, we can define data as object or function, but we know that in components if data is object, the data will interact because object is a reference data type.

In Vue3, data only accepts function and returns an object through function; Mixins’ merge behavior also changes. When mixins merge with data in the base class, shallow copy merge is performed:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack'.id: 1.address: {
          prov: 2.city: 3,},}}}}const Component = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2.address: {
          prov: 4,},}}}}// vue2 result:
{
  id: 2.name: 'Jack'.address: {
    prov: 4.city: 3}}// vue3 result:
user: {
  id: 2.address: {
    prov: 4,}}Copy the code

As we see the result of the final merge, vue2. X will make a deep copy of the data in data. Vue3, however, only performs shallow copy and does not merge copies if data in data already exists.

In ve2. X, we can also handle the presentation of some text content through the filter:

<template> <div>{{ status | statusText }}</div> </template> <script> export default { props: { status: { type: Number, default: 1 } }, filters: {statusText(value){if(value === 1){return 'order not ordered'} else if(value === 2){return 'order to be paid'} else if(value === 3){ Return 'Order completed'}}}} </script>Copy the code

The most common is to deal with some orders of copy display; However, in VUe3, the filter has been removed and is no longer supported, and the official recommendation is to use method calls or computed attributes instead.

v-model

In ve2. X, v-model is equivalent to binding the value attribute to the input event, which is essentially a syntactic sugar:

<child-component v-model="msg"></child-component>
<! -- Equivalent to -->
<child-component :value="msg" @input="msg=$event"></child-component>
Copy the code

In some cases, we need to bind multiple values bidirectionally, and the other values need to be changed explicitly using the callback function:

<child-component 
    v-model="msg" 
    :msg1="msg1" 
    @change1="msg1=$event"
    :msg2="msg2" 
    @change2="msg2=$event">
</child-component>
Copy the code

In Vue2.3.0 +, the.sync modifier, which is essentially syntactic sugar, was introduced to bind the @update:propName callback to the component, which has a cleaner syntax:

<child-component 
    :msg1.sync="msg1" 
    :msg2.sync="msg2">
</child-component>

<! -- Equivalent to -->

<child-component 
    :msg1="msg1" 
    @update:msg1="msg1=$event"
    :msg2="msg2"
    @update:msg2="msg2=$event">
</child-component>
Copy the code

Vue3 integrates the functions of v-model and. Sync, and abandons. Sync, indicating that multiple bidirectional binding values can be directly transmitted by multiple V-Models. Change prop name from value to modelValue:

<child-component 
    v-model="msg">
</child-component>

<! -- Equivalent to -->
<child-component 
  :modelValue="msg"
  @update:modelValue="msg = $event">
</child-component>
Copy the code

If we want to pass multiple values through the V-Model, we can pass an argument to the V-Model:

<child-component 
    v-model.msg1="msg1"
    v-model.msg2="msg2">
</child-component>

<! -- Equivalent to -->
<child-component 
    :msg1="msg1" 
    @update:msg1="msg1=$event"
    :msg2="msg2"
    @update:msg2="msg2=$event">
</child-component>
Copy the code

V – and for the key

In ve2. X, we know that the V-for loop requires a unique key for each child node and cannot be bound to the template tag.

<template v-for="item in list">
  <div :key="item.id">.</div>
  <span :key="item.id">.</span>
</template>
Copy the code

In Vue3, the key value should be placed on the template tag so that we don’t have to set it for each child node:

<template v-for="item in list" :key="item.id">
  <div>.</div>
  <span>.</span>
</template>
Copy the code

V – bind to merge

In vue2. X, if an element defines both v-bind=”object” and an identical separate attribute, that separate attribute overrides the binding in object:

<div id="red" v-bind="{ id: 'blue' }"></div>
<div v-bind="{ id: 'blue' }" id="red"></div>

<! -- The result is the same -->
<div id="red"></div>
Copy the code

However, in VUe3, if an element defines both V-bind =”object” and an identical separate attribute, the order in which the binding is declared determines the final result (the latter overrides the former) :

<! -- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<! -- result -->
<div id="blue"></div>

<! -- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<! -- result -->
<div id="red"></div>
Copy the code

V – for ref

In vue2. X, using the ref attribute on v-for yields an array via this.$refs:

<template
  <div v-for="item in list" :ref="setItemRef"></div>
</template>
<script>
export default {
  data(){
    list: [1, 2]
  },
  mounted () {
    // [div, div]
    console.log(this.$refs.setItemRef) 
  }
}
</script>
Copy the code

But that may not be the desired outcome; So instead of automatically creating an array, vue3 changes the handling of ref to a function that passes this node by default:

<template
  <div v-for="item in 3" :ref="setItemRef"></div>
</template>
<script>
import { reactive, onUpdated } from 'vue'
export default {
  setup() {
    let itemRefs = reactive([])

    const setItemRef = el => {
      itemRefs.push(el)
    }

    onUpdated(() => {
      console.log(itemRefs)
    })

    return {
      itemRefs,
      setItemRef
    }
  }
}
</script>
Copy the code

V-for and V-IF priorities

In ve2. X, both V-for and V-if are used on the same element. V-for has a higher priority, so it is important that v-for and V-IF cannot be placed on the same element for performance optimization in Ve2.

In VUE3, v-if has a higher priority than V-for. So the following code, in vue2. X, works fine, but in vue3 there is no item variable when v-if takes effect, so an error is reported:

<template> <div v-for="item in list" v-if="item % 2 === 0" :key="item">{{ item }}</div> </template> <script> export default { data() { return { list: [1, 2, 3, 4, 5], }; }}; </script>Copy the code

conclusion

The above are some new features and functions that Vue3.0 may involve as a terminal. In fact, there are still many changes in Vue3.0, which are not explained here due to the length. You can check the official documents by yourself, and expect Vue3 to bring us a more convenient and friendly development experience.

For more front-end information, please pay attention to the public number [front-end reading].

If you think it’s good, check out my Nuggets page. Please visit Xie xiaofei’s blog for more articles