This article lists some of the most important updates to Vue3, on which the blog is based

start

Directory structure:

- - - the SRC - | - main. Js - | - App. Vue -- index. HTML -- package. Json -- vite. Config. JsCopy the code

Depend on the package:

npm install vite @vitejs/plugin-vue -D
npm install vue@next -S
Copy the code

Package. Json:

"scripts": {
    "serve": "vite"."build": "vite build"
},
Copy the code

Vite. Config. Js:

module.exports = {
    plugins: [require("@vitejs/plugin-vue") (the)]}Copy the code

Index.html:

<script src="./src/main.js" type="module"></script>
Copy the code

SRC/main. Js:

import { createApp } from "vue"
import App from "./App.vue"

createApp(App).mount("#app");
Copy the code

setup

Setup runs before beforeCreate and created, and there is no **this** in setup

If the returned object is fully mounted to the component instance

If setup returns a function, the function is executed and its return value is returned as a vnode in a safe manner, similar to JSX. Vite requires this for JSX @vitejs/ plugin-vue-vue-jsx: 1, render function returns JSX; Setup returns as a function. They are essentially vNodes returned by h functions

Render > template render > template

<template>
  <div>{{count}}</div>
</template>

<script>
import {
  defineComponent,
  ref,
  h,
} from "vue";
const Comp1 = defineComponent({
  setup() {
    return () = > h("span"."asdasd"); }});const Comp2 = defineComponent({
  setup() {
    return {
        count: ref(0)}; }});export default Comp2
</script>
Copy the code

component

Vue3 recommends that components use defineComponent for component definition. DefineComponent takes a configuration object or function as an argument. There are two configuration methods for configuration objects:

  1. The traditionaloptionalConfiguration method
  2. setupConfiguration method

There are several ways to define a component, but the first is recommended

<template>
  <h1>{{ count }}</h1>
</template>

<script>
import { defineComponent, ref } from "vue";

// defineComponent Passes in a setup configuration component
const Comp = defineComponent({
  setup(props, context) {
    const countRef = ref(0);
    return {
      count: countRef.value, }; }});// Components with setup configuration
const SetUpComp = {
  setup(props, context) {
    const countRef = ref(0);
    return {
      count: countRef.value, }; }};// Optional component with defineComponent
const OptionalWithDefineComp = defineComponent({
  data() {
    return {
      count: 0}; }});// There is no defineComponent optional component
const OptionalWithoutDefineComp = {
  data() {
    return {
      count: 0}; }};// defineComponent The component that is passed into the function configuration
const FunctionComp = defineComponent(function (props, context) {
  const countRef = ref(0);
  return {
    count: countRef.value,
  };
});
export default OptionalWithoutDefineComp;
</script>
Copy the code

Asynchronous components

Define asynchronous components in VUE using defineAsyncComponent to create a dynamically loaded component. There are two ways to create it:

  1. Sketch way

Sketching passes in a Promise factory function.

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() = > import('./components/AsyncComponent.vue'))

export default AsyncComp
Copy the code
  1. All written way

Full write mode, passed in a variety of configurations:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  // Load the component's factory function
  loader: () = > import('./components/AsyncComponent.vue'),
 
  // Loading component when loading component
  loadingComponent: LoadingComponent,
  
  // Failed to load the error component
  errorComponent: ErrorComponent,
  
  // Displays the delay before loading components
  delay: 200.// Load component timeout, if timeout, display error component, default: Infinity
  timeout: 3000.In Suspense, components can be suspended or not. In Suspense, components can be separated from loading and error
  suspensible: false./ * * * *@param {*} Error Indicates the error information object *@param {*} Retry function that can be called to reload a component when it fails to load *@param {*} Fail Exits the loaded function. Fail and retry can only be called together@param {*} Attempts Number of current retries */
  onError(error, retry, fail, attempts) {
    if (error.message.match(/fetch/) && attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})
Copy the code

Render’s duck type

All components of VUe3 can be written as duck type components with render, as in the following example:

// vue3 removes the first argument createElement from the render function and extracts it as the named exported h function used to create vNodes
import { defineAsyncComponent, h } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () = >
    new Promise((resolve, reject) = > {
      setTimeout(() = > {
        if (Math.random() < 0) {
          resolve({
            render() {
              return h("div"."temp component"); }}); }else {
          reject("some thing wrong"); }},2000);
    }),
  loadingComponent: {
    render() {
      return h("div"."loading"); }},errorComponent: {
    render(. args) {
      return h(
        "div",
        {
          style: {
            color: "red",}},"error"); ,}}});Copy the code

Property inheritance

The child component places the undeclared received prop and event from the parent component at $attrs, and adds all attributes of $attr to the attribute of the root node of the child component, or if the component has no root node (Vue3 allows components to have multiple root nodes, React Fragment is an implicit Fragment.)

A non-prop property is inherited from the root component of the quilting component and added to the attribute of the root component node. The same rules apply to event listeners (deprecated. Native event modifiers due to this mechanism).

This operation can be modified with inheritAttrs

// Counter.vue
<template>
  <div>
      <h1>counter</h1>
  </div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  emits: ['change'].inheritAttrs: true,})</script>


// App.vue
<template>
  <Counter style="color: red" @click="alert('111')"></Counter>
</template>

<script>
import {
  defineComponent,
  ref
} from "vue";
import Counter from "./Counter.vue";
const Comp = defineComponent({
  components: {
    Home,
  },
  setup() {
    return {
      alert: (arg) = > {
        window.alert(arg); }}; }});export default Comp;
</script>

Copy the code

instruction

Vue3 has a lot of updates to the directive, including the lifecycle and the built-in directive V-Model

Instruction life cycle

Vue3’s instruction life cycle is similar to the component life cycle, with seven life cycles:

Created, beforeMount, Mounted, beforeUpdate, updated, beforeMount, and unmounted all have 4 parameters.

View the official document in detail: v3.cn.vuejs.org/api/applica…

<template>
  <div @click="$emit('click', 'asdasd')" v-log.server>
    <h1>sad</h1>
  </div>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
  inheritAttrs: false.emits: ["change"].created() {
    console.log(this.$attrs);
  },
  directives: {
    log: {
      created(el, binding) {},
      beforeMount(el, binding) {},
      mounted(el, binding) {
        if (binding.modifiers.server) {
          console.log("sending log to server");
        }
        else {
          console.log('normal log'); }},beforeUpdate(el, binding) {},
      updated(el, binding) {},
      beforeUnmount(el, binding) {},
      unmounted(el, binding){},}}});</script>

Copy the code

v-model

In Vue2, both V-Model and sync are used for bidirectional binding. For details on the difference between the two and how to encapsulate them, click blog.csdn.net/qq_40566547…

But in Vue3, sync is removed, leaving only the V-Model, so for the V-Model, it must be able to pass parameters in order to keep the same functionality as Sync.

Vue3’s V-Model combines sync’s encapsulation rules, namely: properties and @update: properties, and can use custom modifier passes for parameters. Here’s an example:

// App.vue
<tempalte>
	<Editor v-model:checked="checked" v-model:value.trim.cap="value"></Editor>
</tempalte>
<script>
import {
  defineComponent,
  ref
} from "vue";
import Editor from "./components/Editor.vue";
const Comp = defineComponent({
  components: {
    Home,
    Editor,
  },
  setup() {
    return {
      alert: (arg) = > {
        window.alert(arg);
      },
      checked: ref(true),
      value: ref(""),}; }});</script>

// components/Editor.vue
<template>
  <div class="check-editor">
    <div class="check-editor-inner">
      <div class="checkbox" :class="{ checked }" @click="handleCheck"></div>
      <input type="text" class="editor" :value="value" @input="handleInput" />
    </div>
  </div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
  props: {
    checked: Boolean.value: String.valueModifiers: { // Modifier is the modifier used when a component uses V-model :value. XXX and the rule is' ${arg}Modifiers'
      default: {}}}.emits: ['update:checked'.'update:value'].// combine the rules of the sync modifier, and the event must be received here otherwise it will be put in $attrs
  setup(props, context) {
    const handleCheck = () = > {
      context.emit("update:checked", !props.checked);
    };
    const handleInput = (e) = > {
      let value = e.target.value;
      if (props.valueModifiers.cap) {
        value = value.replace(/(\s+|^)(\w)/g.($0) = > $0.toUpperCase());
      }
      if (props.valueModifiers.trim) {
        value = value.trim();
      }
      context.emit("update:value", value);
    };

    return{ handleCheck, handleInput, }; }});</script>

<style scoped>
.check-editor {
  cursor: pointer;
}
.check-editor-inner {
  display: flex;
  align-items: center;
}
.checkbox {
  width: 15px;
  height: 15px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
  border-radius: 3px;
  transition: 0.2 s;
  display: flex;
  align-items: center;
  justify-content: center;
}
.checkbox:hover..checkbox.checked {
  border-color: #409eff;
}
.checkbox.checked::after {
  content: "";
  border-radius: 2px;
  width: 9px;
  height: 9px;
  background: #409eff;
}
.editor {
  border: none;
  outline: none;
  margin-left: 5px;
  border-bottom: 1px solid #dcdfe6;
  font-size: inherit;
}
</style>
Copy the code

Dynamic instruction name

For ** V-on, v-bind**, dynamic command names can exist, but not for v-slot, because the short form does not allow dynamic names, you can see my translation of RFC documentation for details: github.com/ainuo5213/v… Advertise (I will follow up the translation of RFC documents)

<template>
  <div v-if="show">
    <label>Account:</label>
    <input type="text" />
  </div>
  <div v-else>
    <label>Password:</label>
    <input type="text" />
  </div>
  <button@ [key] ="show = ! show">switch</button>
</template>

<script>
import {
  defineComponent,
  ref,
} from "vue";
export default defineComponent({
  setup(props, context) {
    return {
      show: ref(false),
      key: 'click'}; }});</script>

Copy the code

Custom instruction parameters

Vue3 allows the modifier to be wrapped around instructions themselves, as shown in the v-Model above.

data

responsive

Object. DefineProperty is used for data responsiveness in VUe2, and each Object needs to be recursed to the bottom. One problem is that if the data is too large, a large number of events will be consumed in the recursive process of reactive data.

Vue3 uses Proxy+Reflect for data response. Due to the characteristics of Proxy, no matter how large the data is, we only need new Proxy(data, {… }), and does not need recursion, very fast.

Define the way

Vue3 introduces two existing data sources: Data and Setup, due to the vue2-compliant data definition method

A simple example:

import {
  defineComponent,
  ref,
} from "vue";

// Use data to return reactive data
const OptionalWithDefineComp = defineComponent({
  components: {
    Home,
  },
  data() {
    return {
      count: 0}; }});Use setup to return responsive data
const SetupComp = {
  setup(props, context) {
    const countRef = ref(0);
    return {
      count: countRef.value, }; }};Copy the code

ref

Ref is often used to declare stored responsive data, such as timers, primitives, and DOM, but it is not out of the question to store more complex data structures, such as objects.

Refs can be encapsulated by Reactive. The difference is that vue in the template has special “unboxing” operations for Refs and can render directly without taking value

// App.vue
<template>
    <Counter></Counter>
</template>

<script>
import {
  defineComponent
} from "vue";
import Counter from "./components/Counter.vue";
const Comp = defineComponent({
  components: {
    Counter,
  }
});
export default Comp;
</script>


// components/Counter.vue
<template>
    <div ref="domRef">
        domRef
    </div>
    <button @click="myRef.value++">{{myRef.value}}</button>
</template>

<script>
import { defineComponent, ref, onMounted, onUnmounted } from "vue";
export default defineComponent({
  setup() {
    const timerRef = ref(null);
    onMounted(() = > {
      timerRef.value = setInterval(() = > {
        console.log("timer calling");
      }, 2000);
    });
    onUnmounted(() = > {
      clearInterval(timerRef.value);
      timerRef.value = null;
    });

    const domRef = ref(null);

    onMounted(() = > {
        console.log(domRef.value);
    });

    return {
        domRef,
        
        // Use Reactive to encapsulate similar refs. Because the vUE compiler has special "unboxing" operations for refs in the template, the template can be rendered directly without the value
        myRef: reactive({
            value: 1})}}});</script>

<style></style>

Copy the code

shallowRef

For shallowRef, if the value inside is an object, you must assign a new object or use triggerRef to trigger the side effects manually

<template>
  <! Value!! Because there is "unpacking" operation -->
  <button @click="increase">{{ shaRef.count }}</button>
</template>

<script>
import { defineComponent, shallowRef, triggerRef } from "vue";
export default defineComponent({
  setup() {

    const shaRef = shallowRef({
        count: 1
    });

    const increase = () = > {
        shaRef.value = {
            count: shaRef.value.count + 1
        }
        // triggerRef(shaRef);
    }

    return{ shaRef, increase }; }});</script>
Copy the code

reactive

Returns responsive data in object form. If the value of the attribute passed is ref, there will be an “unboxing” operation, as shown in the example above

The life cycle

Optional

Common life cycles for Optional components include:

BeforeCreate: Called after setup for data listening and event/listener configuration

Created: Called after Setup when $EL is not available but data listening, calculation properties, methods, and event callbacks are configured

BeforeMount: Called before mounting to the DOM

Mounted: A component is mounted to the DOM. You can use $el and $refs to get the actual DOM.

BeforeUpdate: called after a component’s data and props are changed before updating to the DOM.

Updated: True DOM update render completes call.

Activated: used when a component cached in the stack by keep-alive is activated.

Deactivated: Called when keep-alive is deactivated by the cache component (not because the component has expired, but because the DOM is removed by a component switch)

BeforeUnmount: Called before a component is unmounted, when component instance methods are still available

Unmounted: Component instance methods, listeners, directives, and subcomponents are unmounted or bound

BeforeUnmount, unmounted can do some things that are clear about side effects, but it is recommended to handle beforeUnmount

setup

For setup configured components, the common declaration cycles are:

BeforeCreate, created => setup beforeMount => onBeforeMount mounted => onMounted beforeUpdate => onBeforeUpdate updated => onUpdated beforeUnmount => onBeforeUnmount unmounted => onUnmounted activated => onActivated deactivated => onDeactivatedCopy the code

For components configured in setup mode, their life cycle can be declared multiple times. Similar to Hooks such as React useEffect, a data dependency can be implemented to process code segments corresponding to a series of life cycle functions

<script>
import {
  defineComponent,
  ref,
  onMounted,
  onUnmounted,
  reactive,
  shallowRef,
  triggerRef,
} from "vue";
export default defineComponent({
  setup() {
    // handle timer
    const timerRef = ref(null);
    onMounted(() = > {
      timerRef.value = setInterval(() = > {
        console.log("timer calling");
      }, 2000);
    });
    onUnmounted(() = > {
      clearInterval(timerRef.value);
      timerRef.value = null;
    });

    // handle dom ref
    const domRef = ref(null);
    onMounted(() = > {
      console.log(domRef.value);
    });

    return{}; }});</script>
Copy the code

Declare periodic function execution to be synchronous execution (hook callback synchronous execution if there is asynchronous code that follows the event loop rules), so for the same lifecycle, the first one is executed first.

The plug-in

Vue3 plug-in can be a method or an object. Generally speaking, we write vue3 plug-in as an object. If it is an object, it requires a static install method in the object. In the Setup configuration, since this is not available at this time, getCurrentInstance is used to get the component instance

// main.js
import { createApp } from "vue"
import App from "./App.vue"
class Plugin {
    static install(app, options) {
        app.config.globalProperties.alert = msg= > {
            window.alert(msg);
        }
    }
}
createApp(App).use(Plugin).mount("#app");

// components/Counter.vue
<template>
  <div ref="domRef">domRef</div>
  <button @click="increase">{{ shaRef.count }}</button>
</template>

<script>
import {
  defineComponent,
  ref,
  getCurrentInstance
} from "vue";
export default defineComponent({
  setup() {

    const shaRef = shallowRef({
      count: 1});// getCurrentInstance gets the current component instance, which can only be used in setup or lifecycle functions
    const app = getCurrentInstance();
    const increase = () = > {
      shaRef.value.count = shaRef.value.count + 1;
      triggerRef(shaRef);
       
      // Use the instance methods injected into Vue from the plug-in
      app.appContext.config.globalProperties.alert("alert");
    };
    

    return{ shaRef, increase, }; }});</script>

Copy the code

Method and compute properties

methods

The vue3 and Vue2 methods can be configured in the optional mode, but the setup mode is recommended, as shown in the preceding example

Calculate attribute

Computed attributes in VUe3 are declared using computed data, which returns a ref type of responsive data in two ways:

Examples come from official documentation

  1. Shorthand: Passes a callback function that executes when a dependency changes to the ref. The calculation property is read-only
const count = ref(1)
const plusOne = computed(() = > count.value + 1)

console.log(plusOne.value) / / 2

plusOne.value++ / / error
Copy the code
  1. Full writing: Pass a stringget,setFunction, used to create read-write objects
const count = ref(1)
const plusOne = computed({
  get: () = > count.value + 1.set: val= > {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value)
Copy the code

Instance properties and methods

In Vue3, we can still use this.$XXX to get Vue exposed apis, such as $emit, $EL, $(app), $refs, $watch, $nextTick, $props, etc

Setup setup components, however, cannot be instantiated because this is not available in setup. However, they can be used through the Vue library exposed to external methods such as context.emit, context.slots, context.attrs, etc. Context is the second parameter of setup, and the first parameter is props

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

<script>
import {
  defineComponent,
} from "vue";
export default defineComponent({
  setup(props, context) {
      console.log(props, context);
  },
  created() {
    console.log(this); }});</script>

Copy the code

efficiency

The following code can see the result of compiling the Vue in the request

F12 > Network > xxx.vue > Preview

Static ascension

Static node

Vue3’s compiler removes the template with no dynamic content to form a static VNode currently available in the scope, avoiding repeating the creation of a static VNode each time render is called.

// Static node of vue2
render(){
  createVNode("h1".null."Hello World")
  // ...
}

// Vue3 static node
const hoisted = createElementVNode("h1".null."Hello World")
function render(){
  // Just use hoisted
}
Copy the code

Static attributes

Vue3 promotes static attributes, such as class names and custom attributes, to external public, avoiding multiple calls to render to create objects repeatedly.

<div class="user">
  {{user.name}}
</div>
Copy the code
const hoisted = { class: "user" }

function render(){
  createElementVNode("div", hoisted, user.name)
  // ...
}
Copy the code

prestringing

When the compiler encounters a lot of continuous static content, it compiles it directly into a plain string node

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

<script>
import {
  defineComponent
} from "vue";
export default defineComponent({
  setup(props, context) {
    return{}; }});</script>
Copy the code
const hoisted =
createStaticVNode("
      
"
) Copy the code

Caching event handlers

Vue3 adds event handlers to the cache

<template>
  <div @click="click">2</div>
</template>

<script>
import {
  defineComponent
} from "vue";
export default defineComponent({
  setup(props, context) {
    const handleClick = () = > {
        console.log(111);
    }
    return {
        click: handleClick }; }});</script>
Copy the code
render(ctx, _cache){
  return createElementVNode("div", {
    onClick: cache[0] || (cache[0] = (. args) = >(ctx.click && ctx.click(... args))) },"2")}Copy the code

Block Tree

Vue3 excludes static nodes and compares only dynamic content and its nodes when comparing VNodes using diff algorithm.

<form>
  <div>
    <label>Account:</label>
    <input v-model="user.loginId" />
  </div>
  <div>
    <label>Password:</label>
    <input v-model="user.loginPwd" />
  </div>
</form>
Copy the code

Patch the rules:

Furthermore, non-repeating keys are generated by default for V-if and v-else, which is not possible in Vue2.

<template>
  <div v-if="show">
    <label>Account:</label>
    <input type="text" />
  </div>
  <div v-else>
    <label>Password:</label>
    <input type="text" />
  </div>
  <button @click="show = ! show">switch</button>
</template>

<script>
import {
  defineComponent,
  ref,
} from "vue";
export default defineComponent({
  setup(props, context) {
    return {
      show: ref(false)}; }});</script>
Copy the code

In the example above, because v-if and v-else will add key by default, new DOM will be generated when show is switched. However, in Vue2, this code is recognized as a virtual DOM when diff algorithm is performed. When switching, only the content under label is switched, and input is not processed

PatchFlag

Vue3 will mark the dynamic node when creating the node, so that VUE can know where the dynamic node is when performing the diff algorithm comparison. I will only compare whether the dynamic part is the same or not, instead of comparing the node type, key, input type, etc., as in VUE2. You can see this by viewing the last parameter of createElementVNode in the browser

<div class="user" data-id="1" title="user name">
  {{user.name}}
</div>
Copy the code

other

Hidden root node

Vue3 can have multiple root nodes in the template, but there is actually a hidden root node for a Fragment similar to React, corresponding to the Fragment in vue

Friendly TreeShaking

Most of the APIS in VUe2 are mounted to Vue or Vue instances, which is not good for Treeshaking and leads to bulky packaging

Vue3 will export most of the private and common apis as named exports, relying on the ES module to rely on the parser, which will greatly reduce the volume of packaging, packaging only the API used

For treeshaking:

  1. Named export, which handles the import and export of a module and triggers Treeshaking
  2. import * as from,import xxx from,import()Importing modules does not trigger Treeshaking

Priority of V-IF and V-for

In vue2, v-for has a higher priority than v-if, so you can use v-if in v-for. However, vue does not recommend using v-if together, so in vue3 this rule is eliminated. V-if has a higher priority than V-for.

<template>
  <ul>
      <! Age = 'user'; age = 'user'; age = 'user';
      <li v-for="user in users" :key="user.id" v-if="user.age > 18">asdasd</li>
  </ul>
</template>

<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
  setup(props, context) {
    return {
      users: [{id: 1.name: "aaa".age: 12
        },
        {
          id: 2.name: "mmm".age: 20},]}; }});</script>

Copy the code

Teleport portal

Teleport is a built-in component in VUe3, which can change the rendering target of a VNode. It is commonly used for rendering components such as modal box, message, Notification, etc. The parameter to is the selector of the target container

Multiple teleports to the target container append content instead of replacing it

<template>
  <teleport to="#app"> hello </teleport>
</template>

<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
  setup(props, context) {
    return {
      show: ref(false),
      key: "click"}; }});</script>

Copy the code