This is the 30th day of my participation in the Wenwen Challenge

preface

Today is the last day of the challenge. After thinking for a long time, I finally decided to write a personal summary about Vue3, not only as a perfect ending in June, but also as a preparation for July!

The preview contents are as follows:

The difference between Vue2 and Vue3

  1. Refactoring responsive systems, usingProxyreplaceObject.defineProperty.ProxyAdvantage:
  • You can listen directly for changes in array type data
  • The target of listening is the object itself, not the imageObject.definePropertyIterate through each property the same, with some performance improvement
  • More things can be intercepted, providedapply,ownKeys,hasEtc. 13 interceptor methods
  1. newComposition API, better logic reuse and code organization
  2. refactoringVirtual DOM
  • Template compile-time optimizations to compile some static nodes into constants
  • slotOptimization,slotCompiled intolazyFunction,slotRendering decisions are left to child components
  • Extracting and reusing inline events in templates (originally regenerating inline functions for each rendering)
  1. Code structure adjustment, more convenientTree shaking, making the volume smaller
  2. useTypescriptreplaceFlow

Why did Vue3 introduce composition API

First we use the form Vue2. X to implement a simple function

Requirements:

1. Delete the item when clicking on the item list

2. When the Add button is clicked, a row will be added to the list of goods according to the data in the form

The code is as follows:

<template>
  <div class="wrap">
    <section>
      <h6>List of goods</h6>
      <table>
        <thead>
          <td>The serial number</td>
          <td>The name of the</td>
          <td>The unit price</td>
          <td>discount</td>
          <td>After the discount price</td>
        </thead>
        <tbody>
          <tr
            v-for="(fruit, index) in fruits"
            :key="fruit.id"
            @click="remove_item(index)"
          >
            <td>{{ fruit.id }}</td>
            <td>{{ fruit.fruit_name }}</td>
            <td>{{ fruit.price }}</td>
            <td>{{ fruit.discount }}</td>
            <td>{{(fruit.price * fruit.discount).tofixed (2)}} yuan/kg</td>
          </tr>
        </tbody>
      </table>
      <br />
    </section>
    <section>
      <h6>To add an item, type:</h6>
      <form>Product Serial number:<input type="text" v-model="f.id" /><br />Product Name:<input type="text" v-model="f.fruit_name" /><br />Unit Price:<input type="text" v-model="f.price" /><br />Discount discount:<input type="text" v-model="f.discount" /><br />
        <button @click="add_item">add</button>
      </form>
    </section>
  </div>
</template>

<script>
export default {
  name: "App".data: function () {
    return {
      fruits: [{id: 1.fruit_name: "apple".price: 10.discount: 0.8 },
        { id: 2.fruit_name: "banana".price: 3.discount: 0.7 },
        { id: 3.fruit_name: "orange".price: 5.discount: 0.5 },
        { id: 4.fruit_name: "durain".price: 50.discount: 0.8},].f: {
        id: 5.fruit_name: "".price: "".discount: "",}}; },methods: {
    remove_item(index) {
      this.fruits = this.fruits.filter((item, key) = >index ! == key); },add_item(e) {
      e.preventDefault();
      let temp = Object.assign({}, this.f);
      this.fruits.push(temp);
      this.f.id = this.fruits.length + 1;
      this.f.fruit_name = "";
      this.f.price = "";
      this.f.discount = ""; ,}}};</script>
Copy the code

Simple dot style

.wrap{
  width: 600px;
  margin: 10px auto;
  display: flex;
  justify-content:space-around;
  background-color:rgb(253.247.247);
  border-radius:4px;
}
.wrap table thead{
  background-color: deepskyblue;
  font-weight: bold;
  font-size: 0.9 em;
}
.wrap table tr:nth-of-type(2n+1) {background-color:pink;
}
.wrap form{
  font-size: 0.9 em;
}
.wrap button{
  margin-top: 10px;
  width: 100%;
  color: rgb(224.43.31);
  font-weight: 700;
}
Copy the code

The following output is displayed:

In the above example, we can see that using the option API of 2.x, whenever a function is implemented, some data is added to data and business logic is added to methods. Fortunately, there are only two simple functions, but in practice, when many functions are added, it becomes very difficult to find the corresponding data and business logic of a function, and the business logic may not necessarily be in methods, but may also be scattered in alternatives such as computed and watch. Therefore, Vue3.0 introduced composition API to solve the problem of function, data, and business logic decentralization, making the project more modular development and later maintenance

Vue3 uses the V-Model more elegantly

We know how to implement two-way data binding in Vue2.0: v-Model and sync. Typically, a component can only be used for one V-Model, but some components need to have multiple data that can respond in both directions, and that’s where.sync comes in

In Vue3.0, in order to achieve uniformity, a component can have multiple V-models, and.sync was removed. In Vue3.0, v-Model needs to be followed by a modelValue, that is, the attribute name to be bidirectionally bound. Vue3.0 implements multiple V-Models by assigning different Modelvalues to different V-Models

The difference between Composition API and React Hook

From the perspective of React Hook implementation, React Hook determines which useState state comes from the next rendering according to the order of useState calls, so the following restrictions appear

  1. Cannot be called in a loop, condition, or nested functionHook
  2. Make sure to always be in yourReactThe top-level call to a functionHook
  3. useEffect,useMemoSuch functions must manually determine dependencies

The Composition API is based on Vue’s responsive system implementation, compared to React Hook’s

  1. Composition APIThe statement insetupA component instantiation is called only once within a functionsetupAnd theReact HookThis is called every time you rerenderHook,ReacttheGCthanVueMore pressure, more performance thanVueIt’s also slower to speak
  2. Compositon APICan also be used in loops, conditions, and nested functions
  3. Reactive systems automatically implement dependency collection, which in turn optimizes the performance of component partsVueThe interior does it by itself, whileReact HookDependencies need to be passed in manually, and the order of dependencies must be guaranteed so thatuseEffect,useMemoSuch functions correctly capture dependent variables, otherwise the component performance will degrade due to incorrect dependencies

The Compositon API looks better than React Hook, but the Compositon API is designed with React Hook in mind

Ref function

As we know, the Setup function is at the heart of the composition API introduced in Vue3.0

In the setup function, you can use the ref function, which creates a responsive data. When the data changes, the Vue automatically updates the UI. For example, use the ref function to define a variable count

import { ref } from 'vue';

function useChangeCount() {
    let count = ref(0);
    function change_count() {
        count.value += 1;
    }
    return { count, change_count }
}
export default useChangeCount;
Copy the code

Import useChangeCount from “./composition_tiny_js/count” and use it in the setup of the component, exposing variables and functions that need to be used by the outside world via a return

setup() {
  let { count, change_count } = useChangeCount();
  return { count, change_count };
}
Copy the code

This allows the change_count method, the count variable exposed above, to be used externally

<template>
    <div>
      <h1>{{ count }}</h1>
      <button @click="change_count">Am I</button>
    </div>
</template>
Copy the code

Note that:

  1. insetupAll variables or methods defined in thereturn {xxx,xxx}So the outside world can use it
  2. refFunctions can only listen for changes to primitive types, not complex types (such as objects, arrays)

Reactive function

Reactive is used in a similar way to ref in that it turns data into reactive data and automatically updates the UI when the data changes. The difference is that ref is used for basic data types, whereas Reactive is used for complex data types, such as objects and arrays. For example, user is a variable that defines an object type

<template>
  <div>
    <p>{{ user }}</p>
    <button @click="increase">click me! one year later</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "reactive".setup() {
    const user = reactive({ name: "Alice".age: 12 });
    function increase() {
      ++user.age
    }
    return{ user, increase }; }};</script>
Copy the code

As above, when the button is clicked, let the datauser.age+ 1. When Vue finds that the data has changed,UIIt’s automatically updated so we’ve verified it. YesreactiveThe function can turn a complex data type into reactive data. We might as well putreactiveI’m going to print out the result of the function and let’s see, what does it returnAnd what we found is,reactiveThe result is to wrap the passed objectproxyobject

So let’s test that out. What if we pass primitive data types?

<template>
  <div>
    <p>{{ userAge }}</p>
    <button @click="increase">click me! one year later</button>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: "reactive".setup() {
    let userAge = reactive(12);
    function increase() {
      console.log(userAge);
      ++userAge;
    }
    return{ userAge, increase }; }};</script>
Copy the code

The runtime finds that the basic data is passed to Reactive, which does not wrap it as a Porxy object, and the interface does not change when the data changes

Note that the parameters passed in Reactive must be json objects or arrays. If other objects (such as new Date()) are passed, the interface will not update automatically by default. If reactive values are required, they can be reassigned

Note about the setup function

As we know, the setup function is the core entry function to the composite API. There are a few things to note when using it:

  1. setupThe execution time of the function is inbeforeCreateandcreatedbetween
  2. Due to thesetupThe execution time is atcreatedBetween, so the component has just been created, whiledataandmethodsIt’s not initialized yet, so it can’tsetupThe use ofdataandmethods
  3. setupIn thethisPoint to theundefined
  4. setupIt can only be synchronous, not asynchronous

Recursive and non-recursive listening

We know that the REF and Reactive functions are used to turn an ordinary data into a reactive data. When the data changes, the interface updates immediately. In fact, there is another rule, is to deeply listen to each layer of data, we call recursive listening

import { reactive } from "vue";
export default {
  setup() {
   const alice =  { 
      name: "Alice".age: 80.sex: 'female'.child: {name:'Tom'.sex: 'male'.age:59.child: {name:'Frank'.sex: 'male'.age:30.child: {name:'Blue'.sex: 'male'.age:3}}}}const AliceFamily = reactive(alice );
    return{ AliceFamily }; }};Copy the code

In the above example, VUE packages each layer of Alice, the object passed by us, into a proxy object by means of reactive function, and deeply monitors every data of each layer of the object. When any data of any layer changes, VUE will detect and update the corresponding UI ref, similarly. Because ref is reactive by nature

Ref (12) reactive({value:12})Copy the code

The benefit of recursive listening is obvious, you can listen to every change of data, but because you need to listen to every data, when the data is very complex, VUE needs to tell every data to be wrapped by Proxy once, this process is very costly for large data volume. So to solve this problem, VUe3 provides two functions to create shallow listening data, i.e., non-recursive listening. The two functions are:

  1. shallowRef
  2. shallowReactive

Note:

  • If it’s throughshallowRefCreate data, thenvueListening is.valueNot the first layer
  • In additionvue3Also providestriggerRefFunction to manually update the interfaceUI. But triggerReactive is not provided, so there is no way to manually trigger interface updates if the data is reactive.

So one might wonder: when do you use recursive listening and when do you use non-recursive listening? In fact, just remember that non-recursive listening is designed to solve the problem of listening to a large amount of data. Therefore, shallowRef and shallowReactive are only considered when the data is very large. Generally, ref and Reactive are used

The toRaw and markRaw functions

In the setup function, we create reactive data through the ref and Reactive functions, which update the UI every time the data is changed. This problem is very costly in performance

Therefore, if there are some operations that do not need to update the UI interface every time we modify the data, we can obtain the original data before Proxy packaging through the toRaw method provided by VUe3, and then modify the original data to modify the internal data of the corresponding Proxy object. This is changed through raw data modification and does not trigger UI updates

<script>
import { reactive, toRaw } from "vue";
export default {
  setup() {
    const obj1= {name:'alice'};
    const user = reactive(obj1);
    const obj2 = toRaw(user);
    console.log(obj1 === obj2);//true
    function change() {
      obj2.name = 'Frank';
    }
    return{ user, change}; }}; </script>Copy the code

In the example above, the changes are made through the wrapped reactive object User, and the interface is updated immediately. But if you modify the data by modifying the original data obj2, the interface will not be updated. Obj2 ===obj1 shows that toRow is used to fetch raw data from a Proxy object

Get raw data from Reactive. Get raw data from REACTIVE

Reactive ({value: obj}); reactive({value: obj}); reactive({value: obj}); reactive({value: obj}); Because the.value is the original data that was passed in when it was created

import { ref, toRaw } from "vue";
export default {
  setup() {
    const obj1= {name:'alice'};
    const user = ref(obj1);
    const obj2 = toRaw(user.value);
    console.log(obj1,user,obj2);
    return{ user }; }};Copy the code

In cases where we want data to never be traced in future operations, VUe3 provides a method: markRaw

setup() {
   const obj= {name:'alice'.age:18};
   obj= markRaw(obj);
   const user= reactive(obj);
   function change(){
    user.age=19
   }
   return { user , change};
}
Copy the code

In this code, obj is marked by markRaw. When we change obJ into a proxy object, we find that the interface does not update when we call change to change data.

ToRef and toRefs functions

We know that ref can be used to create a responsive data, and toRef can create a responsive data, so what’s the difference between them?

In fact, if the ref function is used to change attributes in an object to reactive data, modifying the reactive data does not affect the original data.

import {ref} from 'vue';
export default {
  name:'App'
  setup(){
    let obj = {name : 'alice'.age : 12};
    let newObj= ref(obj.name);
    function change(){
      newObj.value = 'Tom';
      console.log(obj,newObj)
    }
    return {newObj,change}
  }
}
Copy the code

In the above code, when change is executed, the reactive data changes, but the raw data obJ does not.

And the reason is,refIs a copy of the nature, and the original data has no reference relationship

Ref (obj.name) = ref(‘ Alice ‘) = reactive({value:’ Alice ‘}

If toRef is used to turn attributes in an object into reactive data, modifying the reactive data will affect the original data. Note, however, that modifying reactive data created through toRef does not trigger an update to the UI interface.

So, toRef is essentially a reference, associated with the original data

import {toRef} from 'vue';
export default {
  name:'App'
  setup(){
    let obj = {name : 'alice'.age : 12};
    let newObj= toRef(obj, 'name');
    function change(){
      newObj.value = 'Tom';
      console.log(obj,newObj)
    }
    return {newObj,change}
  }
}
Copy the code

In the above code, when change is executed, the reactive data changes, the original data OBJ does not change, but the UI interface is not updated

Summary:

Ref and toRef

(1). Ref is essentially copy, and modifying responsive data will not affect the original data; The nature of toRef is referential, and modifying reactive data affects the original data

(2). If the ref data changes, the interface will update automatically; ToRef The interface does not update automatically when data changes

(3). ToRef pass participating ref is different; ToRef takes two arguments, the first which object and the second which property of the object

So if you want reactive data to be associated with previous data, and you want to update reactive data without updating the UI, use toRef

Sometimes, we want to change multiple attributes of an object to reactive data, and require the reactive data to be associated with the original data, and update the reactive data without updating the interface. ToRefs can be used to batch set multiple data to reactive data. (toRef can only set one data at a time)

ToRefs takes an object as an argument, iterates through all the attributes of the object, and then calls toRef one by one

For example,

import {toRefs} from 'vue';
export default {
  name:'App'
  setup(){
    let obj = {name : 'alice'.age : 12};
    let newObj= toRefs(obj);
    function change(){
      newObj.name.value = 'Tom';
      newObj.age.value = 18;
      console.log(obj,newObj)
    }
    return {newObj,change}
  }
}
Copy the code

Use the customRef function to customize a ref

As we know, the REF function can create a reactive data that updates the UI as the data is updated

Sometimes we want to be able to display the control dependency trace and trigger the response, so we can use the customRef function to customize a ref

Return customRef()

Note:

The customRef function takes a factory function with two arguments, track for tracing and trigger for triggering the response, and returns an object with get and set methods

Official example: Implementing v-Model with stabilization using custom ref:

<input v-model="text" />
Copy the code
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) = > {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() = > {
          value = newValue
          trigger()
        }, delay)
      },
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello'),}}}Copy the code

In the code above, displaying track() in get means that the data needs to be tracked, and calling trigger() in set means that the UI needs to be updated when the data is modified.

To customize a ref function using the customRef function:

1. The function returns customRef()

2. CustomRef accepts two parameters track and trigger

3. CustomRef returns an object

4. The object returned by customRef must implement the set and GET methods

5. The call track() displayed in GET means that the data needs to be tracked, and the call trigger() displayed in set means that the UI needs to be updated when the data is modified

How do I get elements from the ref attribute

In vue2. X, you can add the ref=’ XXX ‘attribute to the element and then get the corresponding element in the code through this.$refs.xxx

However, there is no such thing as $refs in VUe3, so elements obtained by the ref attribute in VUe3 cannot be retrieved in vue2’s way

Vue3 requires lifecycle methods for the simple reason that the elements in template are not yet mounted to the page when setup is executed, so they must be retrieved after Mounted.

<template>
  <div ref='box'>I am DIV</div>
</template>
<script>
import {ref,onMounted}
export default{
  setup(){
    let box = ref(null);
    onMounted(() = >{
      console.log(box.value)
    });
    return {box}
  }
}
</script>
Copy the code

As shown in the code above, in VUe3, all lifecycle methods are removed and need to be imported directly. I’m importing onMounted here

When the interface is mounted, the onMounted callback is automatically executed to retrieve dom elements

summary

1. How to use lifecycle functions in compositionAPI?

Import the import of any lifecycle function you need and call it in setup

2. How does VUe3 get elements on the interface through ref attribute?

Template is written the same way as vue2. Add a ref=' XXX 'to the element.

In Setup, you create and expose reactive data

When the element is created to fit, the corresponding response data is assigned a value

Once the reactive data is assigned, you can use the lifecycle method to get the corresponding reactive data, the DOM element, in the lifecycle method

The readonly and shallowReadonly functions

Vue3 provides read-only functions: readonly, shallowReadonly, and isReadonly

Their uses and differences are obvious:

Readonly: Used to create a read-only data that is deep read-only or recursive read-only

ShallowReadonly: Also used to create a read-only data, but this read-only is layer 1, not deep read-only

IsReadonly: Checks whether a data is read-only

We know that variables defined by const cannot be changed. What’s the difference between readonly and const?

  1. Const is assignment protected. We use a variable defined by const that cannot be reassigned. But if const is assigned to an object, then the contents of the object can be changed. The reason is that a variable defined by const cannot be changed. That is, the address of the object cannot be changed

  2. Readonly protects properties and cannot reassign values to properties

Teleport components

Teleport, known as a portal component, is used to provide a concise way to specify the parent element of its contents

For example, define a modal box that is originally inside a component, but I want it to be under the body when it pops up, level with #app. The following code

<template>
  <button @click="modalsOpen = true">Popup button</button>
  <teleport to='body'>
    <div v-if="modalsOpen" class="modals">
      <div>
          <p>This is a pop-up box that is passed to the body</p>
          <button @click="modalsOpen = false">Shut down</button>
      </div>      
    </div>
  </teleport>
</template>

<script>
import { ref } from "vue";
export default {
  name: "Modals".setup() {
    let modalsOpen = ref(false);
    return{ modalsOpen }; }};</script>

<style scoped>
.modals{
    background-color: rgba(0.0.0.5);
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.modals div{
    border-radius: 4px;
    width: 300px;
    height: 100px;
    background-color:#fff;
    padding: 20px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    font-size:.9em;
    color:  rgba(0.0.0.9);
}
button{
    padding: 5px;
}
</style>
Copy the code

The following information is displayed:

We found that the Teleport component is also relatively simple to use, with the to attribute being the element selector to send its contents to the specified element

Fragments and emits options

Vue3 components can have multiple roots

We know that in vue2. X, each component can have only one root, which means that we write each component template with a parent element. Vue3 has introduced Fragments to make it easier to write component templates, which means that components in VUe3 can have multiple roots

For example, by creating a fragments.vue, the component template can write multiple roots

<template>
    <div>
        <h1>Fragments</h1>
    </div>
    <div>
        <p>Vue3 introduces Fragments, meaning Fragments</p>
        <p>In other words, vuE3 components can have multiple roots</p>
    </div><! -- You can have more roots --> </template>Copy the code

As above, the change from single roots to multiple roots is not a bit Fragmens, which is where the meaning of Fragmens comes from —-

Vue3 adds the emits option for custom events

Vue3 added the emits option. User-defined events sent by components must be defined in the emits option. One is to avoid situations where custom events fire multiple times when they have the same name as native events, such as the Click event. Second, it gives a better indication of how components work

For example, to create a Emits. Vue, we customize a click event

<template>
  <button @click="$emit('click')">Am I</button>
</template>
<script>
export default {
    //emits:['click']
};
</script>
Copy the code

If the custom event is not written to the emits option, the event will be fired multiple times when the custom event name conflicts with the native event name

<template>
  <Emits @click="onClick"></Emits>
</template>

<script>
import Emits from './components/Emits.vue'
export default {
  name: 'App'.components: {
    Emits
  },
  methods: {onClick(){console.log('clicked!!!!! ')}}}</script>
Copy the code

Therefore, when vue3 writes custom events, it is recommended to write custom events in the emits option

The global API in Vue3 is instead called by the application instance

There are some disruptive changes in VUe3, such as the Global API being changed to application instance calls

There are many global apis in Vue2 that change the behavior of vue. Global apis can cause some problems:

  1. Vue2There is noappThe concept of,new Vue()The resulting root instance acts asapp, so that all created root instancesappAll share the same global configuration, which can contaminate other test cases during testing, making testing difficult
  2. Global configuration also makes it impossible to create multiple different global configurations on a single pageappThe instance

So Vue3 uses the createApp function to return the application instance APP and expose a set of global apis from that app instance

For example, Vue.component in Vue2 is changed to the following format

import { createApp, h } from 'vue'
import App from './App.vue'
import './index.css'

const app = createApp(App)
    .component('comp', { render: () = > h('div'.'i am comp!!! ') })
    .mount('#app')
Copy the code

As you can see, the global component created in Vue2 through a constructor call to Component from Vue is now in the form of an app instance call

Similar changes (vue.component changed to App.component) include:

1. Vue.directive was changed to app.directive

2. Vue.mixin is changed to app.mixin

3. Vue.use is changed to app.use

4. Vue. Config is changed to app.config

5. Vue. Config. IgnoredElements change to app. Config. IgnoredElements

Note: vue.config. productionTip and vue.filter are removed from Vue3

On the Tree – shaking

We know that something called tree-shaking in Vue3 is not new. Some people call it “Tree shaking”.

Tree-shaking, in Judah’s own words, is “pruning” useless modules, so that unused apis don’t end up in the final package

Tree-shaking is not really Vue3 stuff, it’s the functionality of those packaging tools. Just a Vue3 code structure change. When a package tool like Webpack is used to package a project, webPack will not pack the unused code into the final project, which makes the project smaller

Principle: Rely on es6 modular syntax, dead-code will be removed!

Lifecycle function changes in Vue3

As we know, each component goes through a series of initialization procedures when it is created, such as setting up data listeners, compiling templates, mounting instances, and triggering DOM updates when data changes.

Vue2 provides lifecycle hook functions such as beforeCreate, created, beforeMount, Mounted, beforeUpdate, updated, beforeDestory, deStoryed, etc. Used to perform some of the actions we want to perform at different stages of the component

By Vue3, something changed

  1. beforeCreate ---->setup
  2. created ---->setup
  3. beforeMount ---->onBeforeMount
  4. mounted ---->onMounted
  5. beforeUpdate ---->onBeforeUpdate
  6. updated ---->onUpdated
  7. beforeDestory ---->onBeforeUnmount
  8. destoryed ---->onUnmounted

As you can see, the setup function replaces the beforeCreate and Created lifecycle functions, so we consider the setup execution time to be between beforeCreate and Created

For tree-shaking purposes, Vue3’s lifecycle functions are imported from VUE and called directly

// Introduce multiple lifecycle functions from vUE
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, 
  onBeforeUnmount, unMounted} from 'vue'
export default {
  name: 'App'.setup() {
    onBeforeMount(() = > {
      // Execute before mounting
    })
    onMounted(() = > {
      // Execute after mount
    })
    onBeforeUpdate(() = > {
      // Execute before update
    })
    onUpdated(() = > {
      // Execute after update
    })
    onBeforeUnmount(() = > {
      // Execute before component destruction
    })
    unMounted(() = > {
      // Execute after the component is destroyed
    })
    return{}}}Copy the code

END~

Above some personal study notes, not necessarily correct! Hope to learn Vue well next month

June, perfect end!! Then take ~