Vue3.0 knowledge re combing

Posted by bajangerry on Sat, 18 Dec 2021 23:40:48 +0100

1, General information of Vue3

  • More than 2 years of development, 100 + contributors, 2600 + submissions and 600 + PR

  • Vue3 supports most of the features of vue2

  • Better support for Typescript

  • Package size reduced by 41%

    • Optimize the core library volume by shaking tree
  • 55% faster for initial rendering and 133% faster for update rendering

    • Virtual DOM rewriting
    • Optimize the generation of slots
    • Static tree promotion
    • Static attribute promotion
    • Proxy based responsive system
  • 54% less memory

  • Implementation of rewriting virtual DOM and tree shaking

  • Easier maintenance

    • TypeScript + modularization
  • More friendly

    • Cross platform: compiler core and runtime core are platform independent, making Vue easier to use with any platform (Web, Android, iOS)
  • Easier to use

    • With improved TypeScript support, the editor can provide strong type checking and errors and warnings
    • Better debugging support
    • Independent responsive module
    • Composition API

Composition API

  • setup
    • The setup() function is a new attribute provided specifically for components in vue3. It provides a unified entry for us to use the new features of vue3's Composition API
    • The setup function will be executed after beforeCreate and before created
    • The first formal parameter props is used to receive props data, and the second formal parameter context is used to define the context
  • ref and reactive
    • The reactive() function receives a normal object and returns a responsive data object
    • The ref() function is used to create a responsive data object based on the given value. The return value of the ref() function call is an object that contains only one value attribute
  • isRef
    • isRef() is used to determine whether a value is an object created by ref()
  • toRefs
  • The toRefs() function can convert the responsive object created by reactive() into an ordinary object. However, each attribute node on this object is responsive data of ref() type
  • computed
    • Create a read-only calculated property
    • Create readable and writable calculated properties
  • watch
    • Common monitoring and processing can be realized
    • Monitor the specified data source, including: monitor the data source of reactive type and the data source of ref type
    • Monitoring multiple data sources
    • Monitoring can be cleared
  • New lifecycle function
Vue3Vue2
use setup()beforeCreate
use setup()created
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeDestroy
onUnmounteddestroyed
onErrorCapturederrorCaptured
onRenderTrackedrenderTracked
onRenderTriggeredrenderTriggered
  • Custom hooks function

Other new features

  • Teleport - location of the teleport component
  • Suspend - loading interface of asynchronous loading component
  • Modification of global API

2, Create vue3 project

Create using Vue cli (Vue cli is still recommended for creating Vue3 projects)

file: https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## Install or upgrade
npm install -g @vue/cli
## Ensure that the vue cli version is 4.5.0 Above 0
vue --version
## Create project
vue create my-project

Next steps

  • Please pick a preset - select Manually select features
  • Check the features needed for your project - multi select typescript & & router & & vuex, and pay special attention to the space as the selection and enter as the next step
  • Choose a version of Vue.js that you want to start the project with - select 3 x (Preview)
  • Use class style component syntax - enter directly
  • Use Babel alongside TypeScript - enter directly
  • Pick a linter / formatter config - enter directly
  • Use history mode for router? - Direct enter
  • Pick a linter / formatter config - enter directly
  • Pick additional lint features - enter directly
  • Where do you prefer placing config for Babel, ESLint, etc.? - Direct enter
  • Save this as a preset for future projects? - Direct enter

Vue3 initial project visual difference

Differences in project details between Vue3 and Vue2

(1) Entry file

Vue3

main.ts

createApp(App).use(store).use(router).mount('#app')

Vue2

main.js

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

(2) Routing operation

Vue3

router/index.ts (a functional single split is introduced to facilitate tree shaking to remove code, reduce project size and improve performance)

import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";

mode is switched to a functional declaration

const router = createRouter({
  history: createWebHashHistory(),
  routes
});

Vue2

router/index.js (the introduction of the whole module leads to the increase of project size and loss)

import VueRouter from 'vue-router'

mode is a string setting

const router = new VueRouter({
  routes
})

(3) State management

Vue3

store/index.ts

import { createStore } from "vuex";

export default createStore({})

Vue2

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({})

(4) Page import component

Vue3

views/home.vue

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src

@Options({
  components: {
    HelloWorld
  }
})
export default class Home extends Vue {}
</script>

The above code can be modified into the following code content, and the method of defining components can also be implemented by using defineComponent

<script lang="ts">
import { defineComponent } from 'vue'
import HelloWorld from "@/components/HelloWorld.vue";

export default defineComponent({
  name:'Home',
  components:{
    HelloWorld
  }
})
</script>

Vue2

views/home.vue

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}
</script>

(5) Definition of components

Vue3

components/helloworld.vue

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({
  props: {
    msg: String
  }
})
export default class HelloWorld extends Vue {
  msg!: string;
}
</script>

The above code can be modified into the following code content, and the method of defining components can also be implemented by using defineComponent

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name:"HelloWorld",
  props:{
    msg:String
  }
})
</script>

Vue2

components/helloworld.vue

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

Create with vite (peripheral support is not perfect)

file: https://v3.cn.vuejs.org/guide/installation.html

Vite is a Web development build tool driven by native ESM. It is developed based on browser native ES imports in the development environment and packaged based on Rollup in the production environment.

npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev

3, Composition API usage (important)

file:

https://v3.cn.vuejs.org/api/basic-reactivity.html

https://v3.cn.vuejs.org/api/composition-api.html

https://composition-api.vuejs.org/zh/

1) setup

The setup function is a new component option. As an entry point for using the Composition API within a component.

Create a component instance, then initialize props, and then call the setup function. From the perspective of the lifecycle hook, it will be called before the beforeCreate hook

Input:

  • {Data} props
  • {SetupContext} context
<script lang="ts">
export default {
  setup(props,context){
    console.log(props,context)
  }
}
</script>

2) ref

Accepts a parameter value and returns a responsive and changeable ref object

The ref object has a single attribute that points to an internal value value

If an object is passed in ref, the reactive method is called for deep response conversion.

When ref is returned as an attribute of the rendering context (i.e. in the object returned by setup() and used in the template, it will be automatically unrolled without additional writing in the template value

(1) Confirm where this object starts

<script lang="ts">
export default {
  beforeCreate(){
     console.log('beforeCreate()',this) // beforeCreate executes after setup with this object
  },
  setup(props,context){
    console.log('setup()',this) // setup is executed before beforeCreate without this object
  }
}
</script>

(2) Declare ref object

ref difference between Vue3 and Vue2

Different from the ref object of Vue2 (looking for DOM and Component components), the ref in Vue3 is to create a reference object containing responsive data

<script lang="ts">
import { ref } from "vue"
export default {
  beforeCreate(){
     console.log('beforeCreate()',this)
  },
  setup(props,context){
    console.log('setup()',this)
    // Reference object containing responsive data
    const count = ref(0) 
    // count is a reference object that contains the value attribute for storing data
    console.log(count,count.value)
  }
}
</script>

Object needs to be returned before it can be used

If the current count object is not return ed, the interpolated count cannot be displayed in the template page

<script lang="ts">
import { ref } from "vue"
export default {
  beforeCreate(){
     console.log('beforeCreate()',this)
  },
  setup(props,context){
    console.log('setup()',this)
    // Reference object containing responsive data
    const count = ref(0) 
    // count is a reference object that contains the value attribute for storing data
    console.log(count,count.value)
    
    return {
    	count
    }
  }
}
</script>

(3) Function to update responsive data

<template>
<div class="about">
  <h2>{{count}}</h2>
  <hr>
  <button @click="increment">Plus 1</button>
</div>
</template>

<script lang="ts">
import {ref} from "vue"
export default {
  beforeCreate () {
    console.log('beforeCreate()',this)
  },
  // The component object cannot be accessed through this before beforeCreate()
  setup() {
    console.log('setup()', this)
    
    // Reference object containing responsive data
    const count = ref(0) // count is a reference object that contains the value attribute for storing data
    console.log(count, count.value)

    // Function to update responsive data
    const increment = () => {
      count.value++ // It is to operate on the value value, not the reference object
    }

    return { // The properties and methods in the object can be accessed directly by the template
      count,
      increment
    }
  }
}
</script>

3) computed

Use the getter function and return an invariant responsive ref object for the value returned from the getter.

Alternatively, it can use objects with get and set functions to create writable ref objects.

(1) Attribute calculation requires the introduction of the computed method from vue. The default operation is getter

<template>
<div class="about">
  <h2>count: {{count}}</h2>
  <h2>double: {{double}}</h2>
  <hr>
  <button @click="increment">Plus 1</button>
</div>
</template>

<script lang="ts">
import { computed,ref } from "vue"
export default {
  setup() {
    const count = ref(0)

    // The computed attribute is also essentially a ref object
    const double = computed(() => { 
      console.log('double computed', count.value)
      return count.value * 2
    })

    const increment = () => {
      count.value++
    }

    return {
      count,
      increment,
      double,
    }
  }
}
</script>

(2) getter and setter can be split to realize value taking and assignment processing

<template>
<div class="about">
  <h2>count: {{count}}</h2>
  <h2>double: {{double}}</h2>
  <h2>double2: {{double2}}</h2>
  <hr>
  <button @click="increment">Plus 1</button>
</div>
</template>

<script lang="ts">
import { computed,ref } from "vue"
export default {
  setup() {
    const count = ref(0)

    const double = computed(() => { 
      console.log('double computed', count.value)
      return count.value * 2
    })

    // Calculation properties containing getter s and setter s
    const double2 = computed({
      get () {
        return count.value * 2
      },
      set (value: number) {
        count.value = value/2
      }
    })

    const increment = () => {
      count.value++
      setTimeout(() => {
        // Call the calculated setter operation
        double2.value += 2
      }, 1000);
    }

    return {
      count,
      increment,
      double,
      double2
    }
  }
}
</script>

4) reactive

Receives a normal object and then returns the responsive agent object of the normal object

Responsive transformation is "deep": it affects all nested properties inside the object

The Proxy implementation based on ES2015 operates through the Proxy object, and the internal data of the source object is responsive

reactive difference ref

  • ref is used to convert a primitive data type into a data type with reactivity. There are seven primitive data types: String, Number, Boolean, Null, Undefined, Symbol and BigInt
  • When ref and reactive define basic element type data, ref defines the packaged responsive data, while reactive defines the original type (that is, the basic type of reactive definition is not responsive, and the modified data cannot be updated to the template)
  • You can choose whether to use ref or reactive. First, you can choose whether to use ref or reactive as you normally write ordinary js code and select the original type and object type. Second: all scenarios use reactive, but remember to use toRefs to ensure that the attributes of reactive objects remain responsive.

(1) reactive sets the object and interpolates the display

<template>
  <div class="about">
    <p>msg:{{ state.msg }}</p>
    <p>numbers:{{ state.numbers }}</p>
    <p>name:{{ state.person.name }}</p>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // The state object is a proxy object for the original object in reactive
    // Once the properties of the proxy object are manipulated, the internal operations are the properties of the original object
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
    });
    return {
      state,
    };
  },
};
</script>

(2) reactive data update processing

<template>
  <div class="about">
    <p>msg:{{ state.msg }}</p>
    <p>numbers:{{ state.numbers }}</p>
    <p>name:{{ state.person.name }}</p>
    <button @click="state.update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // The state object is a proxy object for the original object in reactive
    // Once the properties of the proxy object are manipulated, the internal operations are the properties of the original object
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
      update: () => {
        state.msg = "Vue3 is very good";
        // Replace array elements directly with Subscripts
        // Vue3 will update automatically, but Vue2 will not update automatically. You need to use the converted array function
        state.numbers[1] = 100;
        // Replace object properties directly by path
        // Vue3 will be updated automatically, and vue.com needs to be utilized in Vue2 Set to update object properties
        state.person.name = "Chinavane";
      },
    });
    return {
      state,
    };
  },
};
</script>

(3) For problems after deconstructing reactive, pay attention to the mode switching of updating numbers and person contents

<template>
  <div class="about">
    <p>msg:{{ msg }}</p>
    <p>numbers:{{ numbers }}</p>
    <p>name:{{ person.name }}</p>
    <button @click="update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive } from "vue";
export default {
  setup() {
    // The state object is a proxy object for the original object in reactive
    // Once the properties of the proxy object are manipulated, the internal operations are the properties of the original object
    const state = reactive({
      msg: "hello Vue3",
      numbers: [1, 2, 3],
      person: {
        name: "Vane",
      },
      update: () => {
        state.msg = "Vue3 is very good";
        // Replace array elements directly with Subscripts
        // Vue3 will update automatically, but Vue2 will not update automatically. You need to use the converted array function
        state.numbers = [1, 100, 3];
        // Replace object properties directly by path
        // Vue3 will be updated automatically, and vue.com needs to be utilized in Vue2 Set to update object properties
        state.person = { name: "Chinavane" };
      },
    });

    return {
      // Problem: once a reactive responsive object is resolved, its properties are no longer responsive properties
      ...state,
    };
  },
};
</script>

(4) Use toRefs to convert each attribute in reactive into a ref responsive data object

...
<script lang="ts">
import { reactive, toRefs } from "vue";
export default {
  setup() {
    ...
    return {
      // Problem: once a reactive responsive object is resolved, its properties are no longer responsive properties
      // Solution: use toRefs to convert each attribute in reactive into a ref responsive data object
      ...toRefs(state),
    };
  },
};
</script>

(5) Using interfaces to constrain the content of data types

...
<script lang="ts">
import { reactive, toRefs } from "vue";

interface State {
  msg: string;
  numbers: number[];
  person: {
    name?: string;
  };
  update: () => void;
}

export default {
  setup() {
    const state: State = reactive({
      ...
    });
    return {
      ...toRefs(state),
    };
  },
};
</script>

5) toRefs

Problem: all attribute values retrieved by the reactive object are non responsive

Solution: using toRefs, you can convert all the original attributes of a reactive object into a reactive ref attribute

6) watch

And options API this.$watch (opens new window) (and corresponding watch (opens new window) Option) fully equivalent

Listen for a data: ref or reactive attribute value (getter function)

Listen for multiple data

(1) Set the ref value count and update the data

<template>
  <div class="about">
    ...
    <p>count:{{ count }}</p>
    <button @click="update">update</button>
  </div>
</template>

<script lang="ts">
import { reactive, ref, toRefs } from "vue";
...
export default {
  setup() {
    const count = ref(0);
    const state: State = reactive({
      ...
      update: () => {
       ...
        count.value++;
      },
    });

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>

(2) Monitor the value of a ref

<script lang="ts">
...
export default {
  	...
    // Monitor a ref value
    watch(count, (newVal, oldVal) => {
      console.log("watch count", newVal, oldVal);
      document.title = count.value.toString();
    });

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>

(3) To monitor an attribute in a reactive object, you need to use an arrow function to point to the monitoring object

<script lang="ts">
...
export default {
  	...
    // Monitor a property in the reactive object. Note: specify the getter that returns it
    watch(
      () => state.msg,
      (newVal, oldVal) => {
        console.log("watch msg", newVal, oldVal);
      }
    );

    return {
      ...toRefs(state),
      count,
    };
  },
};
</script>

(4) Monitor multiple objects

export default {
  	...
	// Monitor multiple objects and output them in Deconstruction mode
	watch(
      [count, () => state.msg],
      ([countNewVal, msgNewVal], [countOldValue, msgOldVal]) => {
        console.log("watch Multivalued deconstruction count", countNewVal, countOldValue);
        console.log("watch Multivalued deconstruction msg", msgNewVal, msgOldVal);
      }
    );

	// Monitor multiple objects and output with a single object
    const stateRef = toRefs(state);
    watch([count, () => state.msg, stateRef.msg, state], (values) => {
      console.log("watch Multivalued", values);
    });

    return {
      ...stateRef,
      count,
    };
  },
};
</script>

4, Compare Vue2 and Vue3 responses (important)

1) Response of vue2

  • core
    • Object: hijack (monitor / intercept) the reading and modification of the existing property value of the object through defineProperty
    • Array: a series of methods to update elements by rewriting the array to update the array
Object.defineProperty(data, 'count', {
    get () {}, 
    set () {}
})
  • problem
    • Object directly adds new attributes or deletes existing attributes. The interface will not be updated automatically
    • Replace the element or update the length directly through the subscript, and the interface will not be updated automatically
    • Object.defineProperty can only hijack the properties of an object, so it needs to traverse each object and property. If the property value is an object, it also needs to traverse deeply. Proxy can hijack the whole object and return a new object.
    • Object.defineProperty itself can monitor the changes of array subscripts, but in Vue, this feature is greatly abandoned in consideration of performance / experience cost performance.
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>introduce Object.defineProperty Role of</title>
</head>

<body>
  <script>
    const obj = {
      name: 'Zhang San',
      age: 18,
    };

    // Value operation
    console.log(obj.name)
    // Assignment operation
    obj.name = 'Li Si'
    console.log(obj.name)

    // Through object Defineproperty can intercept value assignment property operations
    Object.defineProperty(obj, 'name', {
      enumerable: true, // The current property is allowed to be cycled
      // If for in is not allowed, it will be skipped
      configurable: true, // The current property is not allowed to be configured
      get() { // getter
        console.log('Someone got it obj.name Value of')
        return 'I'm not Zhang San'
      },
      set(newVal) { // setter
        console.log('I don't want the value you give', newVal)
      }
    })

    console.log(obj.name) // get demo
    obj.name = 'Li Si' // Demo of set
    console.log(obj)
  </script>
</body>

</html>

2) Vue3's response

  • core
    • Through proxy: intercept any (13) operations on any attribute of data, including reading and writing attribute values, adding attributes, deleting attributes, etc
    • Through reflect: dynamically perform specific operations on the corresponding properties of the proxy object
    • file
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      • https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  • Proxy can not only proxy objects, but also proxy arrays and dynamically added attributes.
new Proxy(data, {
	// Intercept reading attribute values
    get (target, prop) {
    	return Reflect.get(target, prop)
    },
    // Intercept setting property values or adding new properties
    set (target, prop, value) {
    	return Reflect.set(target, prop, value)
    },
    // Block delete attribute
    deleteProperty (target, prop) {
    	return Reflect.deleteProperty(target, prop)
    }
})
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proxy And Reflect</title>
  </head>
  <body>
    <script>
      const user = {
        name: "Vane",
        age: 42,
      };

      /* 
    proxyUser User is the proxy object and user is the proxy object
    All the latter operations operate the internal properties of the proxy object through the proxy object
    */
      const proxyUser = new Proxy(user, {
        get(target, prop) {
          console.log("hijack get()", prop);
          return Reflect.get(target, prop);
        },

        set(target, prop, val) {
          console.log("hijack set()", prop, val);
          return Reflect.set(target, prop, val); // (2)
        },

        deleteProperty(target, prop) {
          console.log("hijack delete attribute", prop);
          return Reflect.deleteProperty(target, prop);
        },
      });
      // Read attribute value
      console.log(proxyUser === user);
      console.log(proxyUser.name, proxyUser.age);
      // Set attribute value
      proxyUser.name = "Chinavane";
      proxyUser.age = 18;
      console.log(user);
      // Add attribute
      proxyUser.sex = "male";
      console.log(user);
      // Delete attribute
      delete proxyUser.sex;
      console.log(user);
    </script>
  </body>
</html>

5, Life cycle

1) vue2. Life cycle of X

2) Life cycle of vue3

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-V7GQzpMA-1630218441814)(TyporaImg/lifecycle_3.png)]

3) Mapping between options API lifecycle options and composite APIs

Vue3Vue2
use setup()beforeCreate
use setup()created
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeDestroy
onUnmounteddestroyed
onErrorCapturederrorCaptured
onRenderTrackedrenderTracked
onRenderTriggeredrenderTriggered
<template>
<div class="about">
  <h2>msg: {{msg}}</h2>
  <hr>
  <button @click="update">to update</button>
</div>
</template>

<script lang="ts">
import {
  ref,
  onBeforeMount, 
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted, 
} from "vue"

export default {
  beforeCreate () {
    console.log('beforeCreate()')
  },

  created () {
    console.log('created')
  },

  beforeMount () {
    console.log('beforeMount')
  },

  mounted () {
    console.log('mounted')
  },

  beforeUpdate () {
    console.log('beforeUpdate')
  },

  updated () {
    console.log('updated')
  },

  beforeUnmount () {
    console.log('beforeUnmount')
  },

  unmounted () {
     console.log('unmounted')
  },
  

  setup() {
    
    const msg = ref('abc')

    const update = () => {
      msg.value += '--'
    }

    onBeforeMount(() => {
      console.log('--onBeforeMount')
    })

    onMounted(() => {
      console.log('--onMounted')
    })

    onBeforeUpdate(() => {
      console.log('--onBeforeUpdate')
    })

    onUpdated(() => {
      console.log('--onUpdated')
    })

    onBeforeUnmount(() => {
      console.log('--onBeforeUnmount')
    })

    onUnmounted(() => {
      console.log('--onUnmounted')
    })
    
    return {
      msg,
      update
    }
  }
}
</script>

4) Custom hooks function

  • Function: extract and encapsulate the repeated functions of multiple components

  • In vue2, you can use mixin technology and in vue3, you can use the custom hooks function

Requirement 1: collect the page coordinates clicked by the user's mouse

(1) Implement functions in components
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
  </div>
</template>

<script lang="ts">
import { ref, onMounted, onUnmounted } from "vue";

export default {
  setup() {
    // Initialize coordinate data
    const x = ref(-1);
    const y = ref(-1);

    // Function to collect click event coordinates
    const updatePosition = (e: MouseEvent) => {
      x.value = e.pageX;
      y.value = e.pageY;
    };

    // Bind and click listen after mounting
    onMounted(() => {
      document.addEventListener("click", updatePosition);
    });

    // Unbind and click listen before uninstallation
    onUnmounted(() => {
      document.removeEventListener("click", updatePosition);
    });

    return { x, y };
  },
};
</script>
(2) Separate functions into custom hooks

hooks/useMousePosition.ts

/* 
  Custom hooks: collect page coordinates clicked by the user's mouse
  */
  import { ref, onMounted, onUnmounted } from 'vue'
  
  export default function useMousePosition () {
    // Initialize coordinate data
    const x = ref(-1)
    const y = ref(-1)
  
    // Function to collect click event coordinates
    const updatePosition = (e: MouseEvent) => {
      x.value = e.pageX
      y.value = e.pageY
    }
  
    // Bind and click listen after mounting
    onMounted(() => {
      document.addEventListener('click', updatePosition)
    })
  
    // Unbind and click listen before uninstallation
    onUnmounted(() => {
      document.removeEventListener('click', updatePosition)
    })
  
    return {x, y}
  }
(3) calling custom hook function in component
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
  </div>
</template>

<script lang="ts">
import useMousePosition from "../hooks/useMousePosition";
export default {
  setup() {
    const { x, y } = useMousePosition();
    return { x, y };
  },
};
</script>

Requirement 2: encapsulate asynchronous requests

(1) Separate functions into custom hooks

hooks/useUrlLoader.ts

/* 
  Sending asynchronous ajax requests using axios
  */
  import { ref } from 'vue'
  import axios from 'axios'
  
  export default function useUrlLoader(url: string) {
  
    const result = ref(null)
    const loading = ref(true)
    const errorMsg = ref(null)
  
    axios.get(url)
      .then(response => {
        loading.value = false
        result.value = response.data
      })
      .catch(e => {
        loading.value = false
        errorMsg.value = e.message || 'unknown error'
      })
  
    return {
      loading,
      result,
      errorMsg,
    }
  }
(2) calling custom hook function in component
<template>
  <div class="about">
    <p>x:{{ x }}</p>
    <p>y:{{ y }}</p>
    <hr />
    <p v-if="loading">Loading...</p>
    <p v-else-if="errorMsg">{{ errorMsg }}</p>
    <p v-else>{{ result }}</p>
  </div>
</template>

<script lang="ts">
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";
export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader(
      "https://dog.ceo/api/breeds/image/random"
    );
    return { x, y, result, loading, errorMsg };
  },
};
</script>

6, Strengthen type check and prompt with TS

1) Use generics

hooks/useUrlLoader.ts

export default function useUrlLoader<T>(url: string) {

  const result = ref<T | null>(null)
  ...
}

2) Definition interface, constraint information, single object

...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";

interface DogResult {
  message: string;
  status: string;
}

export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader<DogResult>(
      "https://dog.ceo/api/breeds/image/random"
    );

    watch(result, (newVal, oldVal) => {
      console.log(result.value?.message); // The interface type constraint is not used, and there is no prompt. If there is an interface type, you can prompt
    });

    return { x, y, result, loading, errorMsg };
  },
};
</script>

3) Definition interface, constraint information, array object

...
<script lang="ts">
import { watch } from "vue";
import useMousePosition from "../hooks/useMousePosition";
import useUrlLoader from "../hooks/useUrlLoader";

interface CatResult {
  breeds: any[];
  id: string;
  url: string;
  width: number;
  height: number;
}

export default {
  setup() {
    const { x, y } = useMousePosition();
    const { result, loading, errorMsg } = useUrlLoader<CatResult[]>(
      "https://api.thecatapi.com/v1/images/search"
    );

    watch(result, (newVal, oldVal) => {
      if (result.value) {
        console.log(result.value[0].url);
      }
    });

    return { x, y, result, loading, errorMsg };
  },
};
</script>

7, Wrap components with defineComponent

  • Problem: configuration options are not prompted
  • Solution: use defineComponent(options)
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  setup (props, context) {
    console.log(props.msg)
    console.log(context.attrs, context.slots, context.emit)
    return {

    }
  }
})
</script>

Topics: Vue Vue.js