At the beginning of the year, the team began to introduce vue3 version to prepare for the full-stack use of TS in the later stage. However, due to the large number of new members in the team, it took some time to prepare for the acceptance of TS. Gu first used THE JS version of VUe3 in small projects. After you are relatively familiar with the new API, we will gradually get involved in TS.

Lifecycle hook

Hook function

We can look directly at the lifecycle diagram to see what kind of lifecycle hooks there are:

The full lifecycle hooks are shown below:

We can see that beforeCreate and Created are replaced by setup. Second, hook names add on; Vue3. X also adds hook functions onRenderTriggered and onrendertrickeda for debugging

Vue3. X has hooks that need to be imported from vue:

<template> <div>{{num}}</div> </template> <script> import { ref, defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered } from "vue"; Export default defineComponent({// beforeCreate and created are vue2 beforeCreate() {console.log("------beforeCreate-----"); }, created() { console.log("------created-----"); }, setup() { console.log("------setup-----"); / / vue3. Write x life cycle in the setup onBeforeMount (() = > {the console. The log (" -- -- -- -- -- - the onBeforeMount -- -- -- -- -- "); }); onMounted(() => { console.log("------onMounted-----"); }); onUpdated(() => { console.log('updated! ') }) onUnmounted(() => { console.log('unmounted! ') }) const num = ref(0) setInterval(() => { num.value++; OnRenderTriggered ((event) => {console.log("------onRenderTriggered-----", event); }) return { num } }, }); </script>Copy the code

OnUpdated and onRenderTriggered are triggered when setInterval is used to change data.

setup

Execution order

export default defineComponent ({
    beforeCreate() {
        console.log("----beforeCreate----");
    },
    created() {
        console.log("----created----");
    },
    setup() {
        console.log("----setup----"); },}) setup beforeCreate createdCopy the code

Warning Due to executionsetupThe component instance has not been created atsetupNone of the optionsthis.

The setup parameters

Setup takes two arguments:

  1. Props: Properties/parameters passed by the component
  2. context

The props accepted in setup is reactive, and because it is reactive, you cannot use ES6 deconstruction, because deconstruction eliminates its reactive.

Example of incorrect code for this rule that causes props to no longer support reactive:

export default defineComponent ({
    setup(props, context) {
        const { name } = props
        console.log(name)
    },
})
Copy the code

If you want to use structures, you need to use the official toRefs, which we’ll cover later.

Setup The second argument, context, does not have access to Vue2’s most commonly used this object, so context provides the three most commonly used properties of this: Attrs, Slot, and emit correspond to the $attr attribute, slot slot, and $emit emit event in vue2. x respectively. These attributes are automatically synchronized to the latest value, so we get the latest value every time we use them.

Reactive, REF and toRefs

In VUe2. X, bidirectional binding is defined in data, but Vue3 uses Reactive and REF to define bidirectional binding data.

So what’s the difference between ref and Reactive?

<template> <div>{{obj.name}}-{{obj.count}}</div> <div>{{basetype}}</div> <div>{{baseTypeReactive}}</div> <div>{{objreactive.count}}</div> </template> <script> import { reactive, ref } from 'vue'; export default { setup() { const obj = ref({ count: 1, name: }) const baseType = ref(2) setTimeout(() => {obj.value.count = obj.value.count + 1 obj.value.name = "" basetype.value += 1 }, 1000) const baseTypeReactive = reactive(6) const objreactive = reactive({ count: 10 }) return { obj, basetype, baseTypeReactive, objreactive } }, } </script>Copy the code

reactive

  • reactiveIs a method for implementing responsive data provided in Vue3.
  • Reactive data is implemented via defineProperty in Vue2 and ES6 in Vue3ProxyTo achieve.
  • The reactive parameter must be an object (JSON/ARR) and cannot represent basic types such as strings, numbers, and Boolean.
  • Essence: Wraps the incoming data into a Proxy object
  • If you pass other objects (such as Date objects) to Reactive
    • By default, the modified object does not enable data binding updates to the interface.
    • If updates are required, reassignment is required. (That is, it is not allowed to manipulate the data directly, and a new data needs to be put in place of the original data)

inreactiveUse basic type parameters

Basic types (numbers, strings, booleans) cannot be created as proxy objects in Reactive, so they cannot be listened to and responsive.

<template>
    <div>
        <p>{{msg}}</p>
        <button @click="c">button</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive(0)

            function c() {
                console.log(msg);
                msg++;
            }
            return {
                msg,
                c
            };
        }
    }
</script>
Copy the code

When we click on the button, we expect the number to change from 0 to 1, but the actual number on the interface does not change at all.

Looking at the console, its output looks like this (I clicked it three times)

prompted

value cannot be made reactive: 0

The output does change, but the change is not fed back to the interface, meaning there is no two-way data binding. Of course, if it is ref, there is no such problem. To use Reactive, we need to convert parameters from primitive types to objects.

<template>
    <div>
        <p>{{msg.num}}</p>
        <button @click="c">button</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive({
                num: 0
            })

            function c() {
                console.log(msg);
                msg.num++;
            }
            return {
                msg,
                c
            };
        }
    }
</script>
Copy the code

The parameter is replaced with the object {num: 0}, at which point the interface changes by clicking the button (I clicked it three times).

Prints the message at the console

It can be seen that MSG is successfully created as a proxy object, and it realizes bidirectional data binding of the object by hijacking the get and set methods of the object.

Deep, internal object changes can also be detected (note the inner in the code below)

<template>
<div>
  <p>{{msg.num.inner}}</p>
  <button @click="c">button</button>
</div>
</template>

<script>
import { reactive } from 'vue'
export default {
  name: 'App',
  setup() {
    let msg = reactive({
      num: {
        inner: 0
      }
    })
    function c() {
      console.log(msg);
      msg.num.inner ++;
    }
    return {
      msg,
      c
    };
  }
}
</script>
Copy the code

Array changes can also be listened for, of course

<template>
    <div>
        <p>{{msg}}</p>
        <button @click="c">button</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive([1, 2, 3])

            function c() {
                console.log(msg);
                msg[0] += 1;
                msg[1] = 5;
            }
            return {
                msg,
                c
            };
        }
    }
</script>
Copy the code

Object arrays are also available

<template>
    <div>
        <p>{{msg}}</p>
        <button @click="push">push</button>
        <button @click="change">change</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive([{
                name: 'lilei',
                age: 12
            }])

            function change() {
                console.log(msg);
                msg[0].age += 1;
            }

            function push() {
                console.log(msg);
                msg.push({
                    name: 'zhaodafa',
                    age: 22
                })
            }
            return {
                msg,
                change,
                push
            };
        }
    }
</script>
Copy the code

Special circumstances:reactiveIn the listeningDateDate format data

If the argument is not an array or an object, but a slightly odder data type, such as Date, then the problem is again.

<template>
    <div>
        <p>{{msg}}</p>
        <button @click="c">button</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive(new Date())

            function c() {
                console.log(msg);
                msg.setDate(msg.getDate() + 1);
                console.log(msg);
            }
            return {
                msg,
                c
            };
        }
    }
</script>
Copy the code

Here, I printed MSG twice first. It can be seen that when I clicked button once, the data of MSG was changed, but the interface was not changed. Meanwhile, we found that MSG was not identified as a proxy in the console.

Even if we put Date in the object, as follows:

<template> <div> <p>{{msg.date}}</p> <button @click="c">button</button> </div> </template> <script> import { reactive } From 'vue' export default {name: 'App', setup() {reactive({date: new date ()}); function c() { console.log(msg); msg.date.setDate(msg.date.getDate() + 1); console.log(msg); } return { msg, c }; } } </script>Copy the code

It still doesn’t work.

Obviously, we need to do something special for this data type.

This is done by reassigning (rather than modifying the original value directly).

<template>
    <div>
        <p>{{msg.date}}</p>
        <button @click="c">button</button>
    </div>
</template>

<script>
    import {
        reactive
    } from 'vue'
    export default {
        name: 'App',
        setup() {
            let msg = reactive({
                date: new Date()
            });

            function c() {
                console.log(msg);
                msg.date.setDate((msg.date.getDate() + 1));
                msg.date = new Date(msg.date);
                console.log(msg);
            }
            return {
                msg,
                c
            };
        }
    }
</script>
Copy the code

Here I used the copy scheme to reassign MSG. Date, and the interface successfully changed (date + 1).

ref

Ref can listen on complex objects as well as underlying data types, as follows:

<template> <! -- Take the value directly, No XXX. Value -- -- > < div > {{obj. Name}} - {{obj. Count}} < / div > < div > {{basetype}} < / div > < div > {{date}} < / div > < / template > < script > import { ref } from 'vue'; export default { setup() { const obj = ref({ count: 1, name: }) const baseType = ref(2) const date = ref(new date ()) setTimeout(() => {obj.value.count = obj.value.count + 1 Value += 1 date.value.setDate((date.value.getDate() + 1))); // date.setDate((date.value.getDate() + 1))); // date = new Date(date); }, 1000) return { obj, basetype, date } }, } </script>Copy the code

Ref listens for the Date type and can change the Date type directly without copying the assignment

Note, however, that the ref listens on objects that need to be assigned and evaluated using xxx.value in the setup method; The value can be directly evaluated on the page

Deconstruction methods: toRefs and toRef

toRefs

  • ToRefs can be thought of as the syntactic sugar of toRef, which iterates through all the attributes of an incoming object to make it responsive.

  • Writing pages through user.name,user.age is cumbersome, but you can’t deconstruct the user directly in Es6, which eliminates its responsiveness. As mentioned above, the solution is to use toRefs; This is consistent with the case above where we said props cannot be directly deconstructed using ES6.

  • ToRefs is used to convert a Reactive object to a normal object whose properties are all REF objects. The specific usage is as follows:

< the template > < div class = "homePage" > < p > the first {{year}}, < / p > < p > name: {{nickname}} < / p > < p > age:  {{ age }}</p> </div> </template> <script> import { defineComponent, reactive, ref, toRefs } from "vue"; export default defineComponent({ setup() { const year = ref(0); Reactive ({nickname: "xiaofan", age: 26, gender: "female"}); SetInterval (() => {year.value++ user.age++}, 1000) return {year, // reRefs... toRefs(user) } }, }); </script>Copy the code

toRef

  • Used to deconstruct regular (non-reactive) objects, the deconstructed variable has a reactive form that changes with the value of the original object (but modifying the value of the existing variable does not reverse the value of the original underlying object)
Const obj1 = {count: 1, name: 'zhang SAN 1'}; let name1 = toRef(obj1, 'name'); //Copy the code
<template> <div style="color: White "> < div > obj1: {{obj1}} < br / > name1: {{name1}} - name1Cp: {{name1Cp}} < / div > < br / > < div > obj2: {{obj2}} <br /> name2: {{ name2 }} </div> </div> </template> <script> import { ref, toRef, defineComponent } from 'vue'; Export default defineComponent({setup() {const obj1 = {count: 1, name: 'zhang 3 1'}; let name1 = toRef(obj1, 'name'); Let {name: name1Cp} = obj1; let {name1Cp} = obj1; Const obj2 = ref({count: 1, name: 'zhang3'}); // Const obj2 = ref(count: 1, name: 'zhang3'}); let name2 = toRef(obj2, 'name'); SetInterval (() => {obj1.count++; Name = 'obj1.count' + obj1.count; // name1 = 'king' + obj1.count; // console.log(name1, obj1); // The name1 value is changed successfully, but the page cannot detect it // console.log(name1Cp); Obj2.value.count ++; Name = 'obj2.value.name' + obj2.value.count; Name2 = '5 '; }, 1000); return { obj1, obj2, name1, name2, name1Cp }; }}); </script> <style lang="less"> .demo { color: @fontsize-color; font-size: @fontsize-level1; } </style>Copy the code

Watch and watchEffect

The watch function listens for a specific data source and performs side effects in the callback function. The default is lazy, meaning that callbacks are executed only when the source data being listened on changes.

watch(source, callback, [options])
Copy the code

Parameter Description:

  • Source: can support string, Object, Function, Array; Used to specify the reactive variable to listen for
  • Callback: Callback function to execute
  • Options: Supports deep, immediate, and Flush options.

Listen to data defined by Reactive

<template> <div>{{nickname}}</div> </template> <script> import { defineComponent, ref, reactive, toRefs, watch } from "vue"; export default defineComponent({ setup() { const state = reactive({ nickname: "xiaofan", age: 20 }); SetTimeout (() => {state.age++}, 1000) watch(() => state.age, (curAge, PreAge) = > {the console. The log (" new values: "curAge," old value: ", preAge); }); return { ... toRefs(state) } }, }); </script>Copy the code

Listen for data defined by ref

Const year = ref(0) setTimeout(() =>{year.value ++},1000) watch(year, (newVal, oldVal) =>{console.log("新值:", newVal, ", oldVal); })Copy the code

Listening for multiple data

In the above two examples, we use two Watches respectively. When we need to listen to multiple data sources, we can merge and listen to multiple data simultaneously:

Watch ([() = > state. The age, year], [[curAge preAge], [newVal, oldVal]) = > {the console. The log (" new values: "curAge," old value: ", preAge); Console. log(" new value :", newVal, "old value :", oldVal); });Copy the code

Listen for complex nested objects

In our actual development, we see complex data everywhere, such as:

Const state = reactive({room: {id: 100, attrs: {size: "140 m2 ", type:" 140 m2 "},},}); Watch (() => state.room, (newType, oldType) => {console.log(" new value :", newType, "old value :", oldType); }, {deep:true});Copy the code

In complex data access, if the third parameter is not useddeep:true, is unable to listen to data changes.

As we mentioned earlier, watch is lazy by default, so when is it not lazy and the callback can be executed immediately? Set the third parameter to immediate: true.

Stop Stop listening

The Watch listener we created in the component will stop automatically when the component is destroyed. If we want to stop a listener before the component is destroyed, we can call the return value of the watch() function as follows:

Const stopWatchRoom = watch(() => state.room, (newType, oldType) => {console.log(" new value :", newType, "old value :", oldType); }, {deep:true}); SetTimeout (()=>{// stop listening stopWatchRoom()}, 3000)Copy the code

There is also a watchEffect function. In my opinion, Watch can meet the needs of monitoring, so why there is a watchEffect function? Although I didn’t get the need for it, I still want to introduce the watchEffect, first of all, to see how it is different from Watch.

import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue";
export default defineComponent({
  setup() {
    const state = reactive({ nickname: "xiaofan", age: 20 });
    let year = ref(0)

    setInterval(() =>{
        state.age++
        year.value++
    },1000)

    watchEffect(() => {
        console.log(state);
        console.log(year);
      }
    );

    return {
        ...toRefs(state)
    }
  },
});
Copy the code

The state and year values are printed first. Then, every second, print the state and year values.

As you can see from the code above, unlike Watch, dependencies are not passed in first. WatchEffect collects them automatically by specifying a callback function. At component initialization, dependencies are collected once, and then callbacks are executed again when the data in the collected dependencies changes.

So the comparison is as follows:

  1. WatchEffect does not require manually passing in dependencies
  2. WatchEffect is executed once to automatically collect dependencies
  3. WatchEffect does not get the pre-change value, only the post-change value

Tips: If you define a non-responsive value, watch and watchEffect will not be able to listen for the value change!!

Get the DOM element using ref

Let’s take a look at the acquisition method in VUe2

<div ref="myRef"></div>
this.$refs.myRef
Copy the code

Vue3 usage: Get a single DOM

<template> <div ref="myRef"> </div> </template> <script> import {ref, onMounted} from 'vue'; export default { setup() { const myRef = ref(null); onMounted(() => { console.dir(myRef.value); }); return { myRef }; }}; </script>Copy the code

Vue3 gets multiple DOM

<template> <div :class="$style.demo"> <div> index) in arr" :key="index" :ref="setRef"> {{ item }} </li> </ul> </div> </template> <script> import { ref, nextTick } from 'vue'; export default { setup() { const arr = ref([1, 2, 3]); // Store dom array const myRef = ref([]); const setRef = el => { myRef.value.push(el); }; nextTick(() => { console.dir(myRef.value); }); return { arr, setRef }; }}; </script> Proxy [[Handler]]: Object [[Target]]: Array(3) 0: LI 1: Li 2: Li length: 3Copy the code

Custom Hooks

In VUe2 mixins can be extracted to implement common logic functions (but their drawbacks won’t be discussed here). In VUe3 mixins can be encapsulated as hooks, and we agree that these “custom hooks” are prefixed with use to distinguish them from normal functions.

UseCount. Js implementation:

import { ref, computed } from "vue";

export default function useCount(initValue = 1) {
    const count = ref(initValue);

    const increase = (delta) => {
        if (typeof delta !== "undefined") {
            count.value += delta;
        } else {
            count.value += 1;
        }
    };
    const multiple = computed(() => count.value * 2)

    const decrease = (delta) => {
        if (typeof delta !== "undefined") {
            count.value -= delta;
        } else {
            count.value -= 1;
        }
    };

    return {
        count,
        multiple,
        increase,
        decrease,
    };
}
Copy the code

Let’s look at using the useCount hook in a component:

<template> <div> <p>count: {{count}}</p> <p> {{multiple}} < / p > < div > < button @ click = "happens ()" > 1 < / button > < button @ click = "decrease ()" > minus one < / button > < / div > < / div > </template> <script> import { defineComponent } from 'vue'; import useCount from ".. /hooks/useCount"; export default defineComponent({ setup() { const { count, multiple, increase, decrease } = useCount(10); return { count, multiple, increase, decrease, }; }}); </script>Copy the code

Replace VUEX’s state management component pINA

Similar to Vuex, Pinia is a state management library for Vue. Pinia supports Vue2 and Vue3

This article only covers the use of Pinia in Vue3, with a slight difference in Vue2. Please refer to the official documentation

It’s very lightweight, just 1 KB; Modular design, on demand, easy to learn.

Now let’s go straight to the code;

main.js

import { setupStore } from '@/store';
import App from './App.vue';

const app = createApp(App);
setupStore(app);  / / store
Copy the code

Src/store/index.js

import { createPinia } from 'pinia';

const store = createPinia();

export function setupStore(app) {
	app.use(store);
}

export { store };
Copy the code

Src/store/modules/app.js

import { defineStore } from 'pinia';
import { store } from '@/store';
import ehvApi from '@/api/ehv.js';
export const useAppStore = defineStore({
	id: 'app'./ / the only id
  // Data store
	state: () = > ({
		currentStation: ' '.stationList: [].pageLoading: false
	}),
	getters: {
		getCurrentStation() {
			return this.currentStation;
		},
		getStationList() {
			return this.stationList; }},// Asynchronous change
	actions: {
		async queryStationList() {
			this.stationList = await ehvApi.stationList();
			return this.stationList;
		},
		setCurrentStation(val) {
			this.currentStation = val; }}});Copy the code

App.vue

<script setup>
import { useAppStore } from '@/store/modules/app.js';
const appStore = useAppStore();

// Retrieves the interface request for action
appStore.queryStationList();

// Get the store information
let selectOptions = computed(() = > appStore.getStationList);
</script>
Copy the code

The event bus mitt.js

Vue2. X uses EventBus for component communication, which acts as an EventBus to manage event dispatch responses by creating a new Vue instance. Due to the change of Vue3. X source code does not support the original writing method, the official recommendation to use mitt.js. Vue3 completely removes the $on, $OFF, and $once methods from the instance. $EMIT is still part of the existing API, but it is currently used to fire events that are attached declaratively by the parent component.

Mitmit.js is smaller than EventBus on a Vue instance, at just 200bytes, and supports listening and bulk removal of all events. It can also be used across frameworks, React or Vue, and even jQuery projects can use the same set of libraries.

NPM install –save mitt

Usage:

Utils/mitt.js

import mitt from 'mitt';
export default mitt();
Copy the code

A.vue

import emitter from '@/utils/mitt'; . . Emitter. Emit ('changeStation', value);Copy the code

B.vue

import emitter from '@/utils/mitt'; // Emitters. On ('changeStation', val => {});Copy the code

For other uses, add events using the on method, remove events using the off method, and clear everything.

import mitt from 'mitt'

const emitter = mitt()

// listen to an event
emitter.on('foo', e => console.log('foo', e) )

// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )

// fire an event
emitter.emit('foo', { a: 'b' })

// clearing all events
emitter.all.clear()

// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten
Copy the code

Read more:

Object.defineProperty vs Proxy

When Vue2. X, we often encountered a problem, data update ah, why the page is not updated? When to update with $set, when to force updates with $forceUpdate, and if you were in trouble at one point. Later in the learning process, I started to contact the source code and realized that the root of everything was Object.defineProperty.

Here’s a quick comparison between Object.defineProperty and Proxy

  1. Object.definePropertyYou can only hijack the properties of an object, whereas a Proxy directly proxies an object becauseObject.definePropertyYou can only hijack the attributes of an object. You need to traverse every attribute of the object, and if the value of the attribute is also an object, you need to recursively perform deep traversal. However, Proxy directly proxies objects without traversal
  2. Object.definePropertyNew attributes need to be manually addedObserveBecause theObject.definePropertyThe object attributes are hijacked, so when adding attributes, you need to iterate over the object and use the new attributes againObject.definePropertyTo hijack. Vue2. X is used to add new attributes to arrays and objects$setTo make sure that the new properties are also responsive,$setInternally also by callObject.definePropertyTo deal with it.

Refer to blog post:

Reactive attention point in [1] vue3 series (4) www.cnblogs.com/fsg6/p/1448…

[2] Public account programmer growth refers to the summary of new features and usage changes of Vue3.0 (used in actual work)