The introduction

Vue3 has been released for some time, and I have time to check it out recently, so I hereby summarize. This paper will be elaborated from the following perspectives:

  • Full support for TypeScript
  • New data responsiveness
  • Performance optimization
  • Tree Shaking
  • Composition API

Full support for TypeScript

Vue2 is notoriously unfriendly to TypeScript support.

We usually need to install vue-class-Component and vue-property-decorator separately. If Vuex is introduced in a project, we also need to add third-party plug-ins such as vuex-class, plus Webpack file configuration. Making TypeScript too expensive to introduce in Vue2.

This has been greatly improved in Vue3.

  • On installation, the Vue Cli has TypeScript tool support built directly into it, and does not require a separate installation.

  • In the official announcement of the NPM package

    As applications grow, the static typing system can help prevent many potential runtime errors, which is why Vue3 is written in TypeScript. This means that using TypeScript in Vue3 doesn’t require any additional tools — it’s supported as a first-class citizen.

  • Define Vue components

    import { defineComponent, PropType } from 'vue';
    interface Student {
      name: string,
      address: string,
      age: number,
    }
    const Component = defineComponent({
      // Type inference is enabled
      props: {
        success: { type: String },
        callback: {
          type: Function as PropType<() = > void>},student: {
          type: Object as PropType<Student>,
          required: true,
        },
      },
      data () {
        return {
          message: 'Vue3 code style'}; },computed: {
        reversedMessage(): string {
          return this.message.split(' ').reverse().join(' '); }},})Copy the code

New data responsiveness

I believe that students who have interviewed Vue will be asked this question by the interviewer. How does Vue2 complete the data response?

Object.defineproperty is a method of data hijacking that recursively converts all attributes of an Object to get and set methods, thus intercepting access and changes to attributes of the Object.

Have you considered the downsides to this?

  • Performance problems with large data: In the Observer approach, all keys are looping and recursing due to the need to intercept every property of the object.
  • Object.definePropertyMethod bottlenecks: The API does not support arrays. Vue2 is a reactive support that is overridden by array methods (link).
  • Cannot detect dynamically adding or deleting object properties:definePropertysetterMethods can’t do that, so we often use instructions$setAssign a value to an attribute.

Let’s take a look at Vue3:

The ES6 Proxy object is used to create a Proxy for an object, enabling interception and customization of basic operations (such as property lookup, assignment, enumeration, function calls, and so on).

This process is for all attributes of the current object, whether original or new, as long as external access to the object, must pass this layer of interception.

const obj = {
  name: 'whisky'.age: '27'.alcohol:[
    { name: 'Macallan' },
    { name: Loehmann Lake},],}const p = new Proxy(obj, {
  get(target, propKey, receiver) {
    console.log('You visited' + propKey);
    return Reflect.get(target, propKey, receiver);
  },
  set(target, propKey, value, receiver) {
    console.log('You set it.' + propKey);
    console.log('new' + propKey + '=' + value);
    Reflect.set(target, propKey, value, receiver); }}); p.age ='20';
console.log(p.age);                 
// You set age
/ / new age = 20
// You accessed age
/ / 20
p.newPropKey = 'New properties';          
console.log(p.newPropKey);         
// You set newPropKey
// New newPropKey= new property
// You accessed newPropKey
/ / new properties

// There is no need to add reactive processing again, despite the new property newPropKey
Copy the code

The operation of Vue3 intercepts objects through Proxy, while defineProperty in Vue2 intercepts objects’ attributes.

Performance optimization

Conclusion: Compared with Vue2, the overall performance is improved twice. The optimization points are as follows:

Static flag PatchFlags

Optimization of diff algorithm — static markup

The DIff algorithm of Vue2 adopts the strategy of full comparison.

Original Strategy:

When the Virtual DOM node changes, a new VNode will be generated, and the VNode will be compared with the oldVNode node. If the difference is found, the new value will be directly modified on the real DOM, and the value of the oldVNode will be replaced with VNode.

<div id="app">
  <h1>The diff Vue2</h1>       // Static node
  <p>The fund is green again today</p>       // Static node
  <div>{{ name }}</div>       // Dynamic node
</div>
function render() {
  with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_c('h1', [_v("Vue2的diff")]), _c('p', [_v("Today the fund is green again.")]), _c('div', [_v(_s(name))])])
  }
}
Copy the code

This strategy of full comparison can no doubt be applied to all VNodes, but by full comparison, it means comparison of all nodes.

In our real business scenario, there are some static elements with tag names, class names and even tag contents that will not change. If these static elements also participate in the replacement process of full comparison, there is no doubt that a time consumption will occur.

Let’s see what Vue3 does:

<div id="app">
  <h1>The diff Vue3</h1>       // Static node
  <p>The fund is green again today</p>       // Static node
  <div>{{ name }}</div>       // Dynamic node
</div>
render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", { id: "app" }, [
    _createVNode("h1".null."Vue3的diff"),
    _createVNode("p".null."Today the fund is green again."),
    _createVNode("div".null, _toDisplayString(_ctx.name), 1 /* TEXT */)))}Copy the code

Above, we find that static tags are actually added according to whether each VNode changes or not during the first step of creating the Virtual DOM. In this way, in the process of comparing VNodes with OldvNodes, only the marked nodes need to be compared.

Where the reference frame of the node change type is an enumeration of PatchFlags:

export const enum PatchFlags {
  TEXT = 1,
  CLASS = 1 << 1,
  STYLE = 1 << 2,
  PROPS = 1 << 3,
  FULL_PROPS = 1 << 4,
  HYDRATE_EVENTS = 1 << 5,
  STABLE_FRAGMENT = 1 << 6,
  KEYED_FRAGMENT = 1 << 7,
  UNKEYED_FRAGMENT = 1 << 8,
  NEED_PATCH = 1 << 9,
  DYNAMIC_SLOTS = 1 << 10,
  HOISTED = -1,
  BAIL = -2
}
Copy the code

Obviously, the value is 1 if only the text changes dynamically, 1 << 2 if the style changes dynamically, and so on. When the combination changes, the corresponding tag node codes are generated by bit operation combination.

Optimization of event binding

The event binding in Vue2 is treated as dynamic binding, but it actually performs the same thing every time the event is clicked, so in Vue3, the event is directly cached and reused to improve performance.

In PatchFlags mentioned above, the value of the dynamic attribute is 1 << 3 and the result is 8. Normally, clicking on the event will be compiled and statically marked according to this value. In this case, adding the event listening cache cacheHandlers will remove the original static flag.

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", _hoisted_1, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.confirmHandler($event)))
    }, "Confirm"),
    _createVNode("span".null, _toDisplayString(_ctx.vue3), 1 /* TEXT */)))}// The event is cached in _cache[1] for the first rendering
If there is an event directly from the cache, there is no need to create an update again to reduce consumption
Copy the code

Static promotion hoistStatic

In the static markup section, we learned that some static elements do not need to be updated, but they still need to be rendered after each creation. At this point, static promotion (for the _hostied element) allows the specified element to be created only once, saving overhead by reusing the first and only creation at render time.

const _hoisted_1 = /*#__PURE__*/_createVNode("div".null."Static lift", -1 /* HOISTED */)
Copy the code

Tree Shaking

What is Tree Shaking? A tree, literally, shakes off unwanted leaves.

Back in the code world, we removed unreferenced code from the JavaScript context during our front-end Webpack packaging phase.

We should all have written the following code in Vue2:

import Vue from 'vue';
Vue.nextTick(() = > {
  // Some DOM related operations
});
Copy the code

TIPS: $nextTick in a single file is essentially the same as vue.nexttick.

Consider a question:

If you don’t use vue. nextTick, or if you prefer setTimeout instead, the nextTick part of the Vue will become dead code, which increases the size of the code but is never used. This is a major performance drag for client-rendered Web apps.

In Vue3, the official team refactored the organization of all global apis so that all apis support Tree Shaking.

import { nextTick } from 'vue';
 
nextTick(() = > {
  // Some DOM related operations
});
Copy the code

Composition API and Practice

The cause of

In the days of Vue2, component reuse was an important part of team development, and the transfer of component content based on slot or prop helped abstract logic.

However, at high levels of data transfer, component content can become confusing. We often need to separate methods and data, which are responsible for adding and subtracting, in different parts of the code block. If you can see it ata glance in a widget, but in components with tens or hundreds of lines, the interaction between functions distributed in data, compute, and Method can become difficult to understand. Increased reading, mental, maintenance, revision burden.

experience

The Composition API gives the user the ability to flexibly organize blocks of component code.

I’ll use a ToDoList to compare the Options API with the Composition API in the following article.

The target

Functional requirements:

  1. ToDo adds, deletes, marks finished, and modifies items
  2. Legacy items
  3. Clear completion

implementation

Let’s take a look at how we would handle this requirement if we used the Options API:

<template>
  <section class="todo-app">
    <header class="header"></header>
    <section class="main"></section>
    <footer class="footer"></footer>
  </section>
</template>
<script>
export default {
  data() {
    return {
      todos: [].// Store an array of toDos
      newTodo: ' '.// The todo item currently added
      editTodo: ' '.// Todo item currently modified
    };
  },
  computed: {
    // Current legacy items
    remaining: function () {
      return this.todos.filter((todo) = >! todo.completed).length; },// Select all logic
    allDone: {
      get: function () {
        return this.remaining === 0;
      },
      set: function (value) {
        this.todos.forEach(function (todo) { todo.completed = value; }); }},},methods: {
    addTodo () {},
    deleteTodo () {},
    editTodo () {},
    doneEdit () {},
    cancelEdit () {},
    removeCompleted () {},
  },
};
</script>
Copy the code

Above, Vue2 ToDoList has been basically completed. To get to Composition API:

// Leave template unchanged
<script>
import { ref, reactive, computed, toRefs } from "vue";
// ref is used to track normal data
// reactive is used to track objects or arrays
// toRefs handles reactive to ref
export default {
  setup() {
    / / the data layer
    const test = ref('this is test');
    const state = reactive({
      todos: [].// Store an array of toDos
      newTodo: ' '.// The todo item currently added
      editTodo: ' '.// Todo item currently modified
    });
    
    / / computed layer
    const remaining = computed(
      () = > state.todos.filter(todo= >! todo.completed).length );const allDone = computed({});
    
    / / the methods
    function addTodo () {},
    function removeTodo () {},
    function editTodo () {},...return {
      ...toRefs(state),
      remaining,
      allDone,
      addTodo,
      removeTodo,
      editTodo,
      ... // Other methods are omitted
    };
  }
</script>
Copy the code

conclusion

By contrast, the Composition API is actually a style that favors hooks.

  1. The new functionsetup
  • It will becreatedExecuted before the life cycle and acceptedprops(for internal access),contextTwo parameters (context object, can passcontextTo access an instancethis).
  1. ref.reactive.toRefs
  • refYou can take an incoming value as a parameter and return a response based on that valueRefObject in which values that are changed and accessed are tracked through modificationstestCan trigger a re-rendering of the template to display the latest values.
  • reactiverefThe difference is simply thatreactiveTo modify an object or array.
  • toRefsCan bereactiveThe created reactive object is converted torefCommon objects.
  1. The relevantcomputed.watch.watchEffect
  • computedUsed to create the computed property. The return value is onerefObject that needs to be introduced separately.
  • watchAs with the method in Vue2, you need to listen for the data and execute its listening callback.
  • watchEffectThe function passed in is executed immediately, listens for its dependencies in a responsive manner, and rerunts the function when its dependencies change.
  1. Life cycle correlation

    Vue3’s lifecycle hooks have been slightly changed:

    Vue2--------------vue3
    beforeCreate  -> setup()
    created       -> setup()
    beforeMount   -> onBeforeMount
    mounted       -> onMounted
    beforeUpdate  -> onBeforeUpdate
    updated       -> onUpdated
    beforeDestroy -> onBeforeUnmount
    destroyed     -> onUnmounted
    activated     -> onActivated
    deactivated   -> onDeactivated
    errorCaptured -> onErrorCaptured
    Copy the code

    As you can see, the original life cycle is basically there, just prefixed with on. Setup creates both beforeCreate and beforeCreate hooks, and creates both beforeCreate and created hooks.

    <script>
    import {
      ref
      onBeforeMount,
      onMounted,
      onBeforeUpdate,
      onUpdated,
      onBeforeUnmount,
      onUnmounted
    } from "vue"
    export default {
      setup() {
        const count = ref(0);
        // Other life cycles are written here
        onBeforeMount (() = > {
          count.value++;
          console.log('onBeforeMount', count.value);
        })
        onMounted (() = > {
          count.value++;
          console.log('onMounted', count.value);
        })
        OnBeforeUpdate and onUpdated do not modify values.
        onBeforeUpdate (() = > {
          console.log('onBeforeUpdate', count.value);
        })
        onUpdated (() = > {
          console.log('onUpdated', count.value);
        })
        onBeforeUnmount (() = > {
          count.value++;
          console.log('onBeforeUnmount', count.value);
        })
        onUnmounted (() = > {
          count.value++;
          console.log('onUnmounted', count.value);
        })
        return{ count, }; }}; </script>Copy the code

Note that all of the life cycles are introduced separately, and we might not use as many hooks in daily development. Bringing in on demand reduces the volume, which is the benefit of Tree Shaking.

At this point, we have a basic understanding of the Composition API. The part about component calls is the same as Vue2, except that we might extract the ToDoList code mentioned above as a function component and reference it in the parent component.

Extract the logic useTodos and use the setup method to return all the data, so we can define the component useTodos:

// useTodos.js
const useTodos = () = > {
  / / the data layer
  const state = reactive({
    todos: [].// Store an array of toDos
    newTodo: ' '.// The todo item currently added
    editTodo: ' '.// Todo item currently modified
  });
  
  / / computed layer
  const remaining = computed(
    () = > state.todos.filter(todo= >! todo.completed).length );const allDone = computed({});
  
  / / the methods
  function addTodo () {}, 
  function removeTodo () {},
  function editTodo () {},...return {
    ...toRefs(state),
    remaining,
    allDone,
    addTodo, 
    removeTodo,
    editTodo,
    ...
  };
}
Copy the code

Now, if we need toDOS components, we can:

<script>
import useTodos from './useTodos';
export default {
  setup () {
    const { remaining, allDone, state ... } = useTodos();

    return {
      remaining,
      allDone,
      state,
      ...
    }
  }
}
</script> 
Copy the code

The last

Vue3 has been released for more than a year, I believe that many developers have realized the charm of it, the above views are combined with the official website and the opinions of the major platform developers for analysis and comparison, hoping to bring help to Vue3 enthusiasts on the road and have been on the road ~

More exciting, please pay attention to our public number “100 bottle technology”, there are not regular benefits!