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.

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

[Web Front-end Engineer (Education)]

[Web Front End Engineer (Social)]

Web Front-end Engineer (VUE) web Front-end Engineer (JS)

“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.compile (vue.compile) reactive Vue.version Vue.compile (vue.compile) var ue. Set var UE. Delete (vue.compile) var ue. Vuex and VuE-Router are also improved using tree-shaking, but the SYNTAX of the API is not changed much:

//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

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 (recommend using setup instead)


Created (Setup is recommended instead of **) **

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.

Reactive apis 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, Name. Value = 'new Learn' // new Learn console.log(book.name); If you want to prevent any changes to read-only data, you 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 or we can use the get and set functions to create a readable ref object: 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

The opposite of responsive listening and 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, }); Watch (() => state.count, (count, prevCount) => {// 1 0 console.log(count, prevCount); }); state.count++; const count = ref(0); // if (count, prevCount, 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 }
);
Copy the code

deepObj.a.b.c = “new hello”; 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) => {// 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:

WatchEffect does not need to manually pass in dependencies. Every time watchEffect is initialized, it executes a callback function to automatically retrieve the original 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.

The Composition API is also one of the most important features of 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, each color represents a function. You 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 invalidated.

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

For example, many children may have doubts. Is it no different from what I wrote in data and methods? Isn’t it just putting 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() {} FragmentCopy the code

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:

React allows you to create nodes that need to be transmitted using the createPortal function. 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 is a built-in component from Vue3 that allows our application to render some 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:

// SRC /main.js import {defineAsyncComponent} from "vue"; const AsyncButton = defineAsyncComponent(() => import("./components/AsyncButton.vue") ); app.component("AsyncButton", AsyncButton); // SRC /views/ home. vue import {defineAsyncComponent} from "vue"; export default { components: { AsyncButton: defineAsyncComponent(() => import(".. /components/AsyncButton") ), }, }; 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:

Asynchronous components failed to load images We returned to the Suspense, it says it is primarily in the component loading render some backup content, it provides two slot slot, a default by default, a fallback to load the status:

< 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

Image asynchronous component loading display placeholder incompatible features Incompatible features are mainly some of the syntax changes with the Vue2. X version, there may be compatibility issues in Vue3.

In Vue2. X, we can define data as object or function, but we know that in components if data is object, data will interact with each other 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结果:
{
  id: 2,
  name: 'Jack',
  address: {
    prov: 4,
    city: 3
  }
}

// vue3结果:
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.

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> <! <child-component :value=" MSG "@input=" MSG =$event"></child-component> Other values need to be displayed 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> <! <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> <! <child-component :modelValue=" MSG "@update:modelValue=" MSG = $event"> </child-component> You can pass an argument to the V-model: <child-component v-model.msg1="msg1" v-model.msg2="msg2"> </child-component> <! <child-component :msg1="msg1" @update:msg1="msg1=$event" :msg2="msg2" @update:msg2="msg2=$event"> </child-component>Copy the code

In Vue2. 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> 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 is incorporated in vue2. X. If an element defines both v-bind=”object” and an identical separate attribute, then that separate attribute overrides the binding in object:

<div id="red" v-bind="{ id: 'blue' }"></div> <div v-bind="{ id: 'blue' }" id="red"></div> <! <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 vue2.x, v-for using the ref attribute, this.$refs returns an array:

<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

In ve2. X, 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 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

Summary Above is Vue3.0 as a terminal we may be involved in some new features and functions, in fact, Vue3.0 has a lot of changes, here due to the length of the reasons not to expand, you can check the official documentation, looking forward to Vue3 can bring us a more convenient and friendly development experience.