About Vue3 and MIT The usage of JS is introduced in another article
Some Vue3 knowledge points sorted out (550)+ 👍)
Demand introduction
Recently, the company has a demand for a mobile page. A page contains multiple floors, each of which is a separate component. Each component has its own logic.
The page is similar to the welfare page of the personal center. Each floor displays the picture of the corresponding gift bag. After entering the page, the pop-up window for receiving the gift bag will pop up automatically on the premise of meeting the conditions.
Control the pop-up window display of each gift bag. The hidden status is written in their respective components. Now the demand is
💡 Only one pop-up window can be displayed at a time
💡 Whether you click confirm or cancel, the second pop-up window will open automatically after closing the previous pop-up window
💡 You can control the order of pop-up display
Solution
Technology stack
- Vue3
- mitt.js
- Promise
thinking
Each pop-up window is regarded as an asynchronous task. Build a task queue according to the preset order, and then manually change the status of the current asynchronous task by clicking the button to enter the next asynchronous task.
Step 1
First write two components to simulate the actual situation
Parent component (page component)
<template> <div> <h1>I am the parent component!</h1> <child-one></child-one> <child-two></child-two> <div class="popup" v-if="showPopp"> <h1>I am the parent component pop-up</h1> </div> </div> </template> <script> import { defineComponent } from 'vue' import ChildOne from './components/Child1.vue' import ChildTwo from './components/Child2.vue' export default defineComponent({ name: '', components: { ChildOne, ChildTwo, }, setup() { //Control pop-up display const showPopp = ref(false) return { showPopp, } }, }) </script>
Sub assembly I
<template> <div> I'm floor one </div> <div class="popup" v-if="showPopp"> <h3>I'm pop-up one</h3> <div> <button @click='cancle'>cancel</button> <button @click='confirm'>determine</button> </div> </div> </template> <script> import { defineComponent, ref } from 'vue' export default defineComponent({ name: '', setup() { //Control pop-up display const showPopp = ref(false) //Logic of cancellation const cancle = () => { showPopp.value = false //do something } //Logic of confirmation const confirm = () => { showPopp.value = false //do something } return { showPopp, cancle, confirm, } }, }) </script>
Sub assembly II
Basically, the logic as like as two peas is used to handle the pop up window. In fact, the logic should be extracted into a hook.
<template> <div> This is floor two </div> <div class="popup" v-if="showPopp"> <h3>I'm pop-up two</h3> <div> <button @click='cancle'>cancel</button> <button @click='confirm'>determine</button> </div> </div> </template> <script> import { defineComponent, ref } from 'vue' export default defineComponent({ name: '', setup() { //Control pop-up display const showPopp = ref(false) //Logic of cancellation const cancle = () => { showPopp.value = false //do something } //Logic of confirmation const confirm = () => { showPopp.value = false //do something } return { showPopp, cancle, confirm, } }, }) </script>
The results are shown in the figure below
Step 2
We don't use pop-up window first. We use timer and console Log to simulate asynchronous tasks
Parent component
//Omit some of the code that appears above setup() { ....... //Asynchronous tasks to be processed separately by the parent component const taskC = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Asynchronous task of parent component') }, 1000) }) } onMounted(() => { taskC() }) ...... },
Sub assembly I
//Omit some of the code that appears above setup() { ....... const taskA = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Asynchronous task of sub component 1') }, 1000) }) } onBeforeMount(() => { taskA() }) ...... },
Sub assembly II
//Omit some of the code that appears above setup() { ....... const taskB = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Asynchronous task of sub component 2') }, 1000) }) } onBeforeMount(() => { taskB() }) ...... },
Take a look at the results. Because the task queue has not been built and all asynchronous tasks are performed at the same time, the log s of the three components are printed at the same time
Step 3
Use MIT JS to collect asynchronous tasks
Let's start with MIT JS is encapsulated into a tool function
//mitt.js import mitt from 'mitt' const emitter = mitt(); export default emitter;
Before the child component is mounted, the add async tags event is triggered to notify the parent component to collect asynchronous tasks. The parent component listens to the add async tags event and stores the child component's tasks in the array.
Parent component
//Omit some of the code that appears above setup() { ....... // Declare an empty array to hold all asynchronous tasks let asyncTasks = [] //Add asynchronous tasks to the array and collect all asynchronous tasks const addAsyncTasts = (item) => { asyncTasks.push(item) console.log('🚀🚀~ asyncTasks:', asyncTasks) } // Listen for the add async tags event. When an asynchronous task is triggered, add the asynchronous task to the array emitter.on('add-async-tasts', addAsyncTasts) // When the component is unloaded, the listening event is removed and the array is reset to null onUnmounted(() => { emitter.off('add-async-tasts', addAsyncTasts) asyncTasks = [] }) .......
Sub assembly I
//Omit some of the code that appears above setup() { ....... onBeforeMount(() => { //Conditional judgment if If the conditions are met here, the parent component will be notified of the collection task emitter.emit('add-async-tasts', taskA) }) .......
Sub assembly II
//Omit some of the code that appears above setup() { ....... onBeforeMount(() => { //Conditional judgment if If the conditions are met here, the parent component will be notified of the collection task emitter.emit('add-async-tasts', taskB) }) .......
Look at the results. I log ged in the collection function of the parent component. You can see that the collection function was triggered twice
Click to see that there are two pieces of data, taskA and taskB. That means our mission has been collected.
Step 4
Custom task order
The way I implement this is to pass in a digital parameter when collecting tasks, and finally sort the task queue according to the number.
Parent component
//Omit some of the code that appears above setup() { ....... //Sorting function const compare = (property) => { return (a, b) => { let value1 = a[property] let value2 = b[property] return value1 - value2 } } //Add asynchronous tasks to the array and collect all asynchronous tasks const addAsyncTasts = (item) => { asyncTasks.push(item) //Sort by order field asyncTasks = asyncTasks.sort(compare('order')) console.log('🚀🚀~ asyncTasks:', asyncTasks) } .......
Sub assembly I
//Omit some of the code that appears above setup() { ....... onBeforeMount(() => { //Conditional judgment if If the conditions are met here, the parent component will be notified of the collection task emitter.emit('add-async-tasts', { fun: taskA, order: 1 }) }) .......
Sub assembly II
//Omit some of the code that appears above setup() { ....... onBeforeMount(() => { //Conditional judgment if If the conditions are met here, the parent component will be notified of the collection task emitter.emit('add-async-tasts', { fun: taskB, order: 2 }) }) .......
Looking at the results, you can see that two tasks are still collected and sorted according to order
We modify the order of sub component 1 to 3, and then verify whether the result is correct
You can see that taskA ranks behind taskB, indicating that the order of our custom asynchronous tasks has also been realized.
Step 5
After the tasks are collected, the next step is to build a task queue
Parent component
//Omit some of the code that appears above setup() { ....... //The instance is mounted and then called to ensure that all tasks are collected, we execute the queue in the onMounted cycle. //Mounted does not guarantee that all sub components are mounted together. If you want to wait until the whole view is rendered, you can use nextTick inside mounted onMounted(() => { nextTick(() => { // Build queue const queue = async (arr) => { for (let item of arr) { await item.fun() } //Return a promise in completion status before continuing the chain call return Promise.resolve() } // Execution queue queue(asyncTasks) .then((data) => { //After the tasks of all child components are completed, perform the tasks of the parent component //If you want to perform the task of the parent component first, you can define order as 0 and save it into the task queue return taskC() }) .catch((e) => console.log(e)) }) }) }) .......
Looking at the results, you can see that all the tasks are carried out in order.
Step 6
Use the real pop-up scene to modify the code
Let's take a brief look at promise. The use of promise is not the content of this article
The state of Promise objects is not affected by the outside world.
Promise object represents an asynchronous operation and has three states:
- Pending (in progress)
- Resolved (completed, also known as completed)
- Rejected (failed)
Only the result of asynchronous operation can determine the current state, and no other operation can change this state. This is also the origin of the name Promise. Its English meaning is "commitment", which means that other means cannot be changed.
However, through practice, it is found that Promise status can be manually modified externally
Refer to the following article for details 👉
How to control its status outside Promise
Since it can be modified, we can add code that can manually modify Promise status in the button click event of the sub component
//Omit some of the code that appears above setup() { ....... //Used to change the state of promise externally let fullfilledFn //Asynchronous task const taskA = () => { return new Promise((resolve, reject) => { showPopp.value = true fullfilledFn = () => { resolve() } }) } //Logic of cancellation const cancle = () => { showPopp.value = false fullfilledFn() } //Logic of confirmation const confirm = () => { showPopp.value = false fullfilledFn() } }) .......
Finally, let's look at the results
All codes
Finally, post all the codes
Parent component
<!-- * @Description: * @Date: 2021-06-23 09:48:13 * @LastEditTime: 2021-07-07 10:34:04 * @FilePath: \one\src\App.vue --> <template> <div> <h1>I am the parent component!</h1> <child-one></child-one> <child-two></child-two> </div> <div class="popup" v-if="showPopp"> <h1>I am the parent component pop-up</h1> </div> </template> <script lang='ts'> import { defineComponent, onMounted, onUnmounted, nextTick, ref } from 'vue' import ChildOne from './components/Child1.vue' import ChildTwo from './components/Child2.vue' import emitter from './mitt' export default defineComponent({ name: '', components: { ChildOne, ChildTwo, }, setup() { //Control pop-up display const showPopp = ref(false) //Sorting function const compare = (property) => { return (a, b) => { let value1 = a[property] let value2 = b[property] return value1 - value2 } } //Asynchronous tasks to be handled separately by the component const taskC = () => { return new Promise((resolve, reject) => { setTimeout(() => { showPopp.value = true resolve() }, 1000) }) } // Declare an empty array to hold all asynchronous tasks let asyncTasks = [] //Add asynchronous tasks to the array and collect all asynchronous tasks const addAsyncTasts = (item) => { asyncTasks.push(item) asyncTasks = asyncTasks.sort(compare('order')) console.log('🚀🚀~ asyncTasks:', asyncTasks) } // Listen to the addAsyncTasts event. When an asynchronous task is triggered, add the asynchronous task to the array emitter.on('add-async-tasts', addAsyncTasts) //When an instance is mounted, calling mounted does not guarantee that all subcomponents are also mounted together. If you want to wait until the whole view is rendered, you can use nextTick inside mounted onMounted(() => { nextTick(() => { // Build queue const queue = async (arr) => { for (let item of arr) { await item.fun() } return Promise.resolve() } // Execution queue queue(asyncTasks) .then((data) => { return taskC() }) .catch((e) => console.log(e)) }) }) // When the component is unloaded, the listening event is removed and the array is reset to null onUnmounted(() => { emitter.off('add-async-tasts', addAsyncTasts) asyncTasks = [] }) return { showPopp, } }, }) </script>
Sub assembly I
<!-- * @Description: * @Date: 2021-06-23 09:48:13 * @LastEditTime: 2021-07-07 10:32:52 * @FilePath: \one\src\components\Child1.vue --> <template> <div> I'm floor one </div> <div class="popup" v-if="showPopp"> <h3>I'm pop-up one</h3> <div> <button @click='cancle'>cancel</button> <button @click='confirm'>determine</button> </div> </div> </template> <script lang='ts'> import { defineComponent, onBeforeMount, ref } from 'vue' import emitter from '../mitt' export default defineComponent({ name: '', setup() { //Control pop-up display const showPopp = ref(false) //Used to change the state of promise externally let fullfilledFn //Asynchronous task const taskA = () => { return new Promise((resolve, reject) => { showPopp.value = true fullfilledFn = () => { resolve() } }) } //Logic of cancellation const cancle = () => { showPopp.value = false fullfilledFn() } //Logic of confirmation const confirm = () => { showPopp.value = false fullfilledFn() } onBeforeMount(() => { //Conditional judgment if emitter.emit('add-async-tasts', { fun: taskA, order: 1 }) }) return { showPopp, cancle, confirm, } }, }) </script>
Sub assembly II
<!-- * @Description: * @Date: 2021-06-23 18:46:29 * @LastEditTime: 2021-07-07 10:33:11 * @FilePath: \one\src\components\Child2.vue --> <template> <div> This is floor two </div> <div class="popup" v-if="showPopp"> <h3>I'm pop-up two</h3> <div> <button @click='cancle'>cancel</button> <button @click='confirm'>determine</button> </div> </div> </template> <script lang='ts'> import { defineComponent, onBeforeMount, ref } from 'vue' import emitter from '../mitt' export default defineComponent({ name: '', setup() { //Used to change the state of promise externally let fullfilledFn //Control pop-up display const showPopp = ref(false) //Asynchronous task const taskB = () => { return new Promise((resolve, reject) => { showPopp.value = true fullfilledFn = () => { resolve() } }) } //Logic of cancellation const cancle = () => { showPopp.value = false fullfilledFn() } //Logic of confirmation const confirm = () => { showPopp.value = false fullfilledFn() } onBeforeMount(() => { //Conditional judgment if emitter.emit('add-async-tasts', { fun: taskB, order: 2 }) }) return { showPopp, cancle, confirm, } }, }) </script>
This plan is the first time to meet this demand. It's certainly not the best plan, but it's also a move. I hope you guys can point out a better and simpler scheme.