A comprehensive summary of the new features of Vue 3.0

Vue3.0 has been updating and optimizing since the first One Piece version was released in September 2020; the official document of the Chinese version has also been released; so as end users, let's take a look at the new functions and features of Vue3.

You Dada shared several highlights of Vue3.0 during the live broadcast at Station B:

  • Performance: performance optimization

  • Tree-shaking support: supports tree-shaking optimization

  • Composition API: Composition API

  • Fragment, 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 has been improved by about 1.3~2 times; the packaged volume is also smaller. If you only write a HelloWorld for packaging, it is only 13.5kb; plus all the runtime features, it is only 22.5kb .

So as end users, what is the difference between us and Vue2.x when developing? Talk is cheap, we still look at the code.

Tree-shaking

One of the most important changes in Vue3 is the introduction of Tree-Shaking. The smaller bundle size brought by Tree-Shaking is obvious. In version 2.x, many functions are mounted on the global Vue object, such as nextTick, nextTick, nextTick, set and other functions, so although we may not use them, these global functions will still be packaged as long as vue is introduced during packaging into the bundle.

In Vue3, all APIs are introduced through ES6 modularization, so that packaging tools such as webpack or rollup can eliminate unused APIs when packaging, and minimize the bundle volume; in main.js, we Such changes can be found:

//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");

The method of creating an app instance has changed from the original one new Vue()to the createApp function; however, some core functions such as the virtualDOM update algorithm and the responsive system will be packaged anyway; the changes brought about by this are the previously configured components globally ( Vue.component), instruction (Vue.directive), mix-in (Vue.mixin) and plug-in (Vue.use), etc. become methods directly mounted on the instance; we call it through the created instance, and the benefit is that An application can have multiple Vue instances, and configurations between different instances will not affect each other:

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

Therefore, the following global APIs of Vue2.x also need to be introduced into ES6 modules:

  • Vue.nextTick

  • Vue.observable is no longer supported, changed toreactive

  • Vue.version

  • Vue.compile (full build only)

  • Vue.set (compatible builds only)

  • Vue.delete (compatible builds only)

In addition, both vuex and vue-router have also been improved using Tree-Shaking, but the syntax of the api has 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,
});

life cycle function

We all know that there are 8 lifecycle functions in Vue2.x:

  • beforeCreate

  • created

  • beforeMount

  • mounted

  • beforeUpdate

  • updated

  • beforeDestroy

  • destroyed

In vue3, a new life cycle function is added setup. The timing of setup execution is beforeCreateexecuted before the life function, so the instance cannot be obtained through this function this; at the same time, in order to unify the naming, it will beforeDestroybe renamed beforeUnmount, destroyedrenamed unmounted, so Vue3 has the following lifecycle functions:

  • beforeCreate (it is recommended to use setup instead)

  • created (it is recommended to use setup instead)

  • setup

  • beforeMount

  • mounted

  • beforeUpdate

  • updated

  • beforeUnmount

  • unmounted

At the same time, vue3 has added a new life cycle hook. We can access the life cycle of the component by adding it before the life cycle function on. We can use the following life cycle hooks:

  • onBeforeMount

  • onMounted

  • onBeforeUpdate

  • onUpdated

  • onBeforeUnmount

  • onUnmounted

  • onErrorCaptured

  • onRenderTracked

  • onRenderTriggered

So how do these hook functions be called? We mount the life cycle hook in the setup, and when the corresponding life cycle is executed, the corresponding hook function is called:

import { onBeforeMount, onMounted } from "vue";
export default {
  setup() {
    console.log("----setup----");
    onBeforeMount(() => {
      // beforeMount代码执行
    });
    onMounted(() => {
      // mounted代码执行
    });
  },
}

new features

After talking about the life cycle, the following are the newly added functions of Vue3 that we are looking forward to.

Responsive API

We can reactivecreate reactive state for JS objects with:

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

reactive is equivalent to that in Vue.observableVue2.x.

The reactive function only accepts complex data types such as object and array.

For some basic data types, such as strings and numbers, we want to make it responsive. Of course, we can also create objects through the reactive function, but Vue3 provides another function 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);

The responsive object returned by ref is a RefImpl object that only contains a value parameter, which is obtained and modified in js through its value attribute; but when rendered in the template, the internal value is automatically expanded, so there is no need to appended to the template .value.

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

Reactive is mainly responsible for complex data structures, while ref mainly deals with basic data structures; but many children's shoes will misunderstand that ref can only deal with basic data, and ref itself can also deal with 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);

When we deal with the properties of some large responsive objects, we would like to use ES6's destructuring 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);

But unfortunately, this would remove its reactiveness; for this case, we can convert the reactive object into a set of refs, which will retain the reactive association with the source object:

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

// 注意这里解构出来的name是ref对象
// 需要通过value来取值赋值
name.value = 'new Learn'
// new Learn
console.log(book.name);

For some read-only data, we want to prevent any changes to it, we can readonlycreate a read-only object by:

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";

Sometimes the value we need depends on the state of other values. In vue2.x, we use it computed函数to calculate attributes. In vue3, the computed function is extracted. It accepts a getter function and creates a value for the value returned by the getter Immutable reactive ref objects:

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 also use the get and set functions to create a readable and writable 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);

responsive listening

Corresponding to computed is watch, computed is a many-to-one relationship, and watch is a one-to-many relationship; vue3 also provides two functions to listen to changes in data sources: watch and watchEffect.

Let's take a look at watch first. Its usage is exactly the same as the component's watch option. It needs to monitor a data source and then execute a specific callback function. Let's first look at how it monitors a single data source:

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

const state = reactive({
  count: 0,
});

//侦听时返回值得getter函数
watch(
  () => state.count,
  (count, prevCount) => {
    // 1 0
    console.log(count, prevCount);
  }
);
state.count++;

const count = ref(0);
//直接侦听ref
watch(count, (count, prevCount) => {
  // 2 0
  console.log(count, prevCount, "watch");
});
count.value = 2;

We can also put multiple values ​​in an array for listening, and the final value is also returned 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;

If we listen to 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";

The final print result can be found to be the changed value. This is because listening to a responsive object always returns a reference to the 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";

Generally, listening will automatically stop when the component is destroyed, but sometimes we want to stop it manually before the component is destroyed. We can call the stop function returned by watch to stop:

const count = ref(0);

const stop = watch(count, (count, prevCount) => {
  // 不执行
  console.log(count, prevCount);
});

setTimeout(()=>{
  count.value = 2;
}, 1000);
// 停止watch
stop();

There is also a function watchEffect that can also be used to listen, but there is already a watch. What is the difference between this watchEffect and watch? Their usage mainly differs in the following points:

  1. watchEffect不需要手动传入依赖
    
  2. 每次初始化时watchEffect都会执行一次回调函数来自动获取依赖
    
  3. watchEffect无法获取到原值,只能得到变化后的值
    
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);

watchEffect will automatically execute once when the page is loaded to track responsive dependencies; when the timer is executed every 1s after loading, watchEffect will monitor the data changes and execute automatically, and each execution will obtain the changed value.

Composition API

Composition API (combination API) is also the most important function in Vue3. The previous 2.x version used Options API(option API), that is, the official definition of writing: data, computed, methods, where you need to write it. Write, the problem brought about by this is that as the function increases, the code becomes more and more complicated. We need to jump up and down repeatedly when looking at the code:

picture

Composition API comparison

In the figure above, a color represents a function, and Options APIthe function codes we can see are scattered; Composition APIthe logic of the same function can be organized inside a function, which is convenient for maintenance.

Let's first look at the previous writing of the Options API:

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

Options APIIt is to put the same type of things in the same option. When our data is relatively small, this organization method is relatively clear; but as the data increases, the function points we maintain will involve multiple data and methods , but we can't perceive which data and methods need to be involved, often need to switch back and forth, and even need to understand the logic of other functions, which also makes the components difficult to understand and read.

What Composition APIwe do is to put the code of the same function together for maintenance, so that when we need to maintain a function point, we don’t need to care about other logic, but only focus on the current function; organize the code through Composition APIoptions setup:

export default {
  setup(props, context) {}
};

We see here that it receives two parameters, props and context. props is some data passed in by the parent component, and context is a context object, which is some properties exposed from 2.x:

  • attrs

  • slots

  • emit

Note: The data of props also needs to be destructured by toRefs, otherwise the responsive data will be invalid.

Let's look at the specific usage of setup through a Button button:

picture

give a chestnut

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

Many children's shoes may have doubts. Is this no different from what I wrote in data and methods? Isn't it just putting them together? We can setupdivide the function extraction into independent functions, and each function can also 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() {}

Fragment

The so-called Fragment is a fragment; in vue2.x, each template is required to have a root node, so our code should be written like this:

<template>
  <div>
    <span></span>
    <span></span>
  </div>
</template>

Or in Vue2.x, you can also introduce vue-fragmentsthe library and replace the div with a virtual fragment; in React, the solution is to React.Fragmentcreate a virtual element through a tag; in Vue3, we don't need the root node directly:

<template>
    <span>hello</span>
    <span>world</span>
</template>

This will save a lot of meaningless div elements.

Teleport

Teleport translates to teleportation and long-distance transmission; as the name implies, it can transmit elements or components in slots to other locations on the page:

picture

portal game

In React, you can use createPortalfunctions to create nodes that need to be transmitted; originally, Youda thought of the name Portal, but the original H5 Portal标签is also planned. Although there are some security issues, in order to avoid duplication of names, it was changed to Teleport.

A common usage scenario of Teleport is to transfer the position of the modal box in some deeply nested components. Although logically the modal box belongs to the component, but in terms of style and DOM structure, the deep nesting level is not conducive to maintenance (z-index and other issues); therefore, we need to strip it out:

<template>
  <button @click="showDialog = true">打开模态框</button>

  <teleport to="body">
    <div class="modal" v-if="showDialog" style="position: fixed">
      我是一个模态框
      <button @click="showDialog = false">关闭</button>
      <child-component :msg="msg"></child-component>
    </div>
  </teleport>
</template>
<script>
export default {
  data() {
    return {
      showDialog: false,
      msg: "hello"
    };
  },
};
</script>

Here, the modal div in Teleport is sent to the bottom of the body; although rendered in different places, the elements and components in Teleport still belong to the logical subcomponents of the parent component, and can still communicate with the parent component. Teleport receives two parameters toand disabled:

  • to - string: must be a valid query selector or HTMLElement, can be id or class selector, etc.

  • disabled - boolean: If it is true, it means that the function of teleport is disabled, and the content of its slot will not be moved to any position. The default is false and it is not disabled.

Suspense

Suspense is a built-in component launched by Vue3, which allows our program to render some back-up content while waiting for asynchronous components, allowing us to create a smooth user experience; loading asynchronous components in Vue actually exists in Vue2.x, The routing component loaded in the vue-router we use is actually an asynchronous component:

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

Redefined in Vue3, asynchronous components need to defineAsyncComponentbe defined through:

// 全局定义异步组件
//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")
    ),
  },
};

At the same time, finer management of asynchronous components can be carried out:

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();
        }
      },
    }),
  },
};

In this way, we can control the loading of asynchronous components, and can reload or display abnormal status when loading fails:

picture

Async component loading failed

Let's go back to Suspense. It is mentioned above that it mainly renders some backup content when the component is loaded. It provides two slots, one defaultdefault and one fallbackloading state:

<template>
  <div>
    <button @click="showButton">展示异步组件</button>
    <template v-if="isShowButton">
      <Suspense>
        <template #default>
          <AsyncButton></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>

picture

Asynchronous component loading display placeholder

Incompatible features

The non-compatible functions are mainly some grammars that have changed greatly from the Vue2.x version, and there may already be compatibility issues on Vue3.

data, mixins, and filters

In Vue2.x, we can define data as objector function, but we know that if data is an object in a component, data will interact with each other, because object is a reference data type;

In Vue3, data only accepts functiontypes by functionreturning objects; at the same time, Mixinthe merging behavior has also changed. When mixin and data in the base class are merged, shallow copy merging will be 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,
  },
}

We see the result of the final merge. Vue2.x will perform a deep copy, and merge and copy the data in the data downward; while vue3 only performs a shallow copy, and will not merge and copy the data in the data if it is found to exist.

In vue2.x, we can also 过滤器filterhandle the display of some text content by:

<template>
  <div>{
   
   { status | statusText }}</div>
</template>
<script>
  export default {
    props: {
      status: {
        type: Number,
        default: 1
      }
    },
    filters: {
      statusText(value){
        if(value === 1){
          return '订单未下单'
        } else if(value === 2){
          return '订单待支付'
        } else if(value === 3){
          return '订单已完成'
        }
      }
    }
  }
</script>

The most common is to process the copywriting display of some orders; however, in vue3, the filter filter has been deleted and is no longer supported. The official suggestion is to use method calls or to replace it 计算属性computed.

v-model

In Vue2.x, it v-modelis equivalent to binding valueproperties and inputevents, and it is essentially a syntactic sugar:

<child-component v-model="msg"></child-component>
<!-- 相当于 -->
<child-component :value="msg" @input="msg=$event"></child-component>

In some cases, we need two-way binding for multiple values, and other values ​​need to be changed explicitly using callback functions:

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

Modifiers are introduced in vue 2.3.0+ , which is essentially syntactic sugar, which is to bind callbacks .syncon components , and the syntax is more concise:@update:propName

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

In Vue3 , the functions are integrated v-modeland .sync.sync is discarded, which means: multiple two-way binding value values ​​can be passed directly with multiple v-models; at the same time, the prop name passed by v-model by default is changed from value became modelValue:

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

<!-- 相当于 -->
<child-component 
  :modelValue="msg"
  @update:modelValue="msg = $event">
</child-component>

If we want to pass multiple values ​​through v-model, we can pass one argumentto 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>

v-for and key

In Vue2.x, we all know that each cycle of v-for needs to give each child node a unique key, which 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 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>

v-bind merge

In vue2.x, if an element defines v-bind="object"the same single property at the same time, then this single property will override the binding objectin:

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

<!-- 最后结果都相同 -->
<div id="red"></div>

However, in vue3, if an element defines v-bind="object"the same single attribute at the same time, 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>

ref in v-for

refIn vue2.x, using attributes on v-for this.$refswill get 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>

But this may not be the result we want; so vue3 no longer automatically creates an array, but changes the processing of ref into a function, which passes in the 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>

v-for and v-if precedence

In vue2.x, using v-for and v-if on an element at the same time v-forhas a higher priority, so when doing performance optimization in vue2.x, an important point is v-for and v-if cannot be placed on the same element.

In vue3, v-ifthan v-forhas a higher priority. Therefore, the following code can run normally in vue2.x, but there is no variable when v-if takes effect in vue3 item, so an error will be 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>

Summarize

The above are some new features and functions that we may be involved in using Vue3.0 as a terminal. In fact, there are still many changes in Vue3.0. Due to space reasons, we will not expand them one by one here. You can refer to the official documents by yourself. Looking forward to Vue3 can bring us a more convenient and friendly development experience.

Guess you like

Origin blog.csdn.net/qq_27318177/article/details/119170748
Recommended