I have introduced the use of Vue3 and mitt.js in another article
Some Vue3 points (550+👍)
Demand is introduced
Recently, the company has a requirement for a mobile page. A page contains multiple floors, each of which is a separate component. Each component has its own internal logic.
The page is similar to the welfare page of the personal center. Each floor displays the picture of the corresponding gift package. After the user enters the page, a popup window for receiving the gift package will pop up automatically if the conditions are met.
Control pop-ups for each package to show the hidden state written separately in the respective component, now required
💡 can only show one popover at a time
💡 No matter click ok or cancel, after closing the previous popover, the second popover will open automatically
💡 can control the pop-up display order
The solution
Technology stack
- Vue3
- mitt.js
- Promise
Train of thought
Each popup is treated as an asynchronous task, and a task queue is constructed in a preset order. Then, click the button to manually change the status of the current asynchronous task and enter the next asynchronous task.
Step one
Let’s write two components to simulate the real world
Parent component (page component)
<template>
<div>
<h1>I'm the parent!</h1>
<child-one></child-one>
<child-two></child-two>
<div class="popup"
v-if="showPopp">
<h1>I'm the parent component popover</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 the popup display
const showPopp = ref(false)
return {
showPopp,
}
},
})
</script>
Copy the code
A child components
<template>
<div>I'm floor one</div>
<div class="popup"
v-if="showPopp">
<h3>I'm popover 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 the popup display
const showPopp = ref(false)
// Cancel logic
const cancle = () = > {
showPopp.value = false
//do something
}
// The validation logic
const confirm = () = > {
showPopp.value = false
//do something
}
return {
showPopp,
cancle,
confirm,
}
},
})
</script>
Copy the code
Child component 2
The logic of the popover is exactly the same as that of the subcomponent. In fact, the logic should be extracted into a hook. Here, for the convenience of demonstration, I will write it directly.
<template>
<div>I'm floor two</div>
<div class="popup"
v-if="showPopp">
<h3>I'm popover 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 the popup display
const showPopp = ref(false)
// Cancel logic
const cancle = () = > {
showPopp.value = false
//do something
}
// The validation logic
const confirm = () = > {
showPopp.value = false
//do something
}
return {
showPopp,
cancle,
confirm,
}
},
})
</script>
Copy the code
The result is shown below
Step 2
Instead of using popovers, let’s go through the timer andconsole.log
To simulate asynchronous tasks
The parent component
// omit some of the previous code
setup(){...// An asynchronous task to be handled separately by the parent component
const taskC = () = > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('Parent component's asynchronous task')},1000)
})
}
onMounted(() = > {
taskC()
})
......
},
Copy the code
A child components
// omit some of the previous code
setup(){...const taskA = () = > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('Asynchronous task for child component one')},1000)
})
}
onBeforeMount(() = > {
taskA()
})
......
},
Copy the code
Child component 2
// omit some of the previous code
setup(){...const taskB = () = > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('Asynchronous task for child component two')},1000)
})
}
onBeforeMount(() = > {
taskB()
})
......
},
Copy the code
Take a look at the result. Since the task queue has not been built yet, all asynchronous tasks are executed at the same time, so the logs of three components are printed out at the same time
Step 3
usemitt.js
To collect asynchronous tasks
Start by wrapping mit.js as a utility function
//mitt.js
import mitt from 'mitt'
const emitter = mitt();
export default emitter;
Copy the code
The add-Async-tasts event is raised before the child component is mounted, notifies the parent component to collect the asynchronous task, listens for the Add-Async-tasts event in the parent component, and stores the child component’s task into an array.
The parent component
// omit some of the previous code
setup(){...// Declare an empty array to hold all asynchronous tasks
let asyncTasks = []
// Add asynchronous tasks to the array collect all asynchronous tasks
const addAsyncTasts = (item) = > {
asyncTasks.push(item)
console.log('🚀 🚀 ~ asyncTasks:', asyncTasks)
}
// Listen for the add-async-tasts event and add the asynchronous task to the array when it is triggered
emitter.on('add-async-tasts', addAsyncTasts)
// The listening event is removed when the component is uninstalled, and the array is reset to empty
onUnmounted(() = > {
emitter.off('add-async-tasts', addAsyncTasts)
asyncTasks = []
})
.......
Copy the code
A child components
// omit some of the previous code
setup(){... onBeforeMount(() = > {
// if... The parent component is notified of the collection task if the condition is met here
emitter.emit('add-async-tasts', taskA)
})
.......
Copy the code
Child component 2
// omit some of the previous code
setup(){... onBeforeMount(() = > {
// if... The parent component is notified of the collection task if the condition is met here
emitter.emit('add-async-tasts', taskB)
})
.......
Copy the code
Looking at the result, I have logged the parent component’s collection function, and you can see that the collection function is triggered twice
If you click on it, you can see that there are two data points in it, namely taskA and taskB. It means our missions are already collected.
Step 4
Customize the task order
The way I implement this is to pass in an extra numeric parameter when collecting tasks, and finally sort the task queue by numeric size.
The parent component
// omit some of the previous code
setup(){...// Sort function
const compare = (property) = > {
return (a, b) = > {
let value1 = a[property]
let value2 = b[property]
return value1 - value2
}
}
// Add asynchronous tasks to the array collect all asynchronous tasks
const addAsyncTasts = (item) = > {
asyncTasks.push(item)
// Sort by the order field
asyncTasks = asyncTasks.sort(compare('order'))
console.log('🚀 🚀 ~ asyncTasks:', asyncTasks)
}
.......
Copy the code
A child components
// omit some of the previous code
setup(){... onBeforeMount(() = > {
// if... The parent component is notified of the collection task if the condition is met here
emitter.emit('add-async-tasts', { fun: taskA, order: 1})})...Copy the code
Child component 2
// omit some of the previous code
setup(){... onBeforeMount(() = > {
// if... The parent component is notified of the collection task if the condition is met here
emitter.emit('add-async-tasts', { fun: taskB, order: 2})})...Copy the code
Looking at the results, you can see that two tasks are still collected and sorted by order
Let’s change the order of child component 1 to 3 to verify that the result is correct
You can see that taskA is behind taskB, indicating that our custom order of asynchronous tasks has also been implemented.
Step 5
Once the tasks are collected, the next step is to build the task queue
The parent component
// omit some of the previous code
setup(){...To ensure that all tasks are collected, we execute the queue in the onMounted cycle
//mounted Does not ensure that all child components are mounted together. If you want to wait until the entire view is rendered, you can use nextTick inside Mounted
onMounted(() = > {
nextTick(() = > {
// Build the queue
const queue = async (arr) => {
for (let item of arr) {
await item.fun()
}
// Return a completed state promise to continue the chain call
return Promise.resolve()
}
// Execute queue
queue(asyncTasks)
.then((data) = > {
// After all the child component tasks are completed, the parent component tasks are performed
// If you want to start the parent component's task first, you can define order as 0 and store it in the task queue
return taskC()
})
.catch((e) = > console.log(e))
})
})
})
.......
Copy the code
Looking at the result, you can see that all the tasks are done in order.
Step 6
Modify the code with a real popover scenario
Let’s start with a quick look at promises, the use of which is not covered in this article
The state of the Promise object is unaffected.
The Promise object represents an asynchronous operation with three states:
- Pending
- Resolved, also known as a pity
- Rejected (failed)
Only the result of an asynchronous operation can determine the current state, and no other operation can change the state. That’s where the name “Promise” comes from. Its English name means “Promise,” indicating that nothing else can change it.
But practice has shown that you can manually change the state of a Promise externally
See the following article for details 👉 How to control the state of a Promise externally
Now that you can change it, add code to manually change the Promise state in the button click event of the child component
// omit some of the previous code
setup(){...// Used externally to change the state of the promise
let fullfilledFn
// Asynchronous tasks
const taskA = () = > {
return new Promise((resolve, reject) = > {
showPopp.value = true
fullfilledFn = () = > {
resolve()
}
})
}
// Cancel logic
const cancle = () = > {
showPopp.value = false
fullfilledFn()
}
// The validation logic
const confirm = () = > {
showPopp.value = false
fullfilledFn()
}
})
.......
Copy the code
Finally, let’s look at the results
All the code
Post the entire code at the end
The 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'm the parent!</h1>
<child-one></child-one>
<child-two></child-two>
</div>
<div class="popup"
v-if="showPopp">
<h1>I'm the parent component popover</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 the popup display
const showPopp = ref(false)
// Sort function
const compare = (property) = > {
return (a, b) = > {
let value1 = a[property]
let value2 = b[property]
return value1 - value2
}
}
// An asynchronous task to be processed 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 collect all asynchronous tasks
const addAsyncTasts = (item) = > {
asyncTasks.push(item)
asyncTasks = asyncTasks.sort(compare('order'))
console.log('🚀 🚀 ~ asyncTasks:', asyncTasks)
}
// Listen for the addAsyncTasts event to add an asynchronous task to the array when it is fired
emitter.on('add-async-tasts', addAsyncTasts)
// Mounted does not guarantee that all child components will be mounted at once. If you want to wait until the entire view is rendered, you can use nextTick inside Mounted
onMounted(() = > {
nextTick(() = > {
// Build the queue
const queue = async (arr) => {
for (let item of arr) {
await item.fun()
}
return Promise.resolve()
}
// Execute queue
queue(asyncTasks)
.then((data) = > {
return taskC()
})
.catch((e) = > console.log(e))
})
})
// The listening event is removed when the component is uninstalled, and the array is reset to empty
onUnmounted(() = > {
emitter.off('add-async-tasts', addAsyncTasts)
asyncTasks = []
})
return {
showPopp,
}
},
})
</script>
Copy the code
A child components
<! -- * @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 popover 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 the popup display
const showPopp = ref(false)
// Used externally to change the state of the promise
let fullfilledFn
// Asynchronous tasks
const taskA = () = > {
return new Promise((resolve, reject) = > {
showPopp.value = true
fullfilledFn = () = > {
resolve()
}
})
}
// Cancel logic
const cancle = () = > {
showPopp.value = false
fullfilledFn()
}
// The validation logic
const confirm = () = > {
showPopp.value = false
fullfilledFn()
}
onBeforeMount(() = > {
// if...
emitter.emit('add-async-tasts', { fun: taskA, order: 1})})return {
showPopp,
cancle,
confirm,
}
},
})
</script>
Copy the code
Child component 2
<! -- * @Description: * @Date: 2021-06-23 18:46:29
* @LastEditTime: 2021-07-07 10:33:11
* @FilePath: \one\src\components\Child2.vue
-->
<template>
<div>I'm floor two</div>
<div class="popup"
v-if="showPopp">
<h3>I'm popover 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 externally to change the state of the promise
let fullfilledFn
// Control the popup display
const showPopp = ref(false)
// Asynchronous tasks
const taskB = () = > {
return new Promise((resolve, reject) = > {
showPopp.value = true
fullfilledFn = () = > {
resolve()
}
})
}
// Cancel logic
const cancle = () = > {
showPopp.value = false
fullfilledFn()
}
// The validation logic
const confirm = () = > {
showPopp.value = false
fullfilledFn()
}
onBeforeMount(() = > {
// if...
emitter.emit('add-async-tasts', { fun: taskB, order: 2})})return {
showPopp,
cancle,
confirm,
}
},
})
</script>
Copy the code
This plan is the first time to meet such a need, beat the head to come up with, certainly not the best plan, but it is a move. I hope you can give me a better and simpler plan.
reference
How do I control the state of a Promise externally