I wrote about it earlierPersonal Practices for React Dialog Components (juejin.cn)My company’s technology stack has always been React, so I have not had enough opportunities to practice VUE3. Using the same thread to wrap the Vue3 Dialog component today, compare the differences between the two frameworks. This article will not be as detailed as the React article in terms of ideas. Those who are interested can first read the React encapsulation article.
Encapsulation useDialog Hook
In React, I use useReducer to encapsulate. One of its features is Immutable. Once data is created, it cannot be changed. However, in VUE, we need to directly change the reactive object, and direct reassignment will result in the invalidation of the responsiveness characteristic. The useEffect used to hide the scroll bar when the Dialog appears will be replaced by watch.
import {reactive, watch} from 'vue';
type DialogStack = ArrayThe < {dialogFlag: string; dialogProps? :object; } >.interface Action {
type: string;
dialogFlag: string; dialogProps? :object;
}
const useDialog = () = > {
const dialogStack: DialogStack = reactive([]);
const dispatch = (action: Action) = > {
switch (action.type) {
case 'close':
// Can not be directly reassigned
while (dialogStack.length) {
dialogStack.pop();
}
break;
case 'open':
dialogStack.push({
dialogFlag: action.dialogFlag,
dialogProps: action.dialogProps,
});
break;
case 'back':
dialogStack.pop();
break; }}; watch(() = > dialogStack.length, (currLen) = > {
document.body.style.cssText = currLen ? 'overflow: hidden; ' : ' ';
});
return {
dialogStack,
dispatch,
};
};
export default useDialog;
Copy the code
Provide/ Inject implements global references
React can use context to implement globals without introducing external libraries. Vue’s solution is Provide/inject
Provide the useDialog data in the app.vue file via provide
<script lang="ts"> import {provide} from 'vue'; import useDialog from '@/hooks/useDialog'; export default { setup() { const {dialogStack, dispatch} = useDialog(); provide('DialogContext', { dialogStack, dispatch }); }}; </script>Copy the code
Use inject to obtain data from the demo. vue file
<script lang="ts">
import {inject} from 'vue';
export default {
name: 'Demo',
setup() {
// @ts-ignore
const {dispatch} = inject('DialogContext')
}
};
</script>
Copy the code
Dialog subcomponents
We will then display the corresponding Dialog sub-component by dispatch an Action (the type of action can be seen in the previous TS annotation). It is recommended that the data and events needed by the Dialog display be passed through the Action
I created two Dialog sub-components to facilitate subsequent demonstrations.
-
AboutUs
<template> <div class="container"> <h1> About Us </h1> <div> <button @click="dialogProps.close"> close </button> <button @click="dialogProps.openElse" class="open-else-margin"> open else </button> </div> </div> </template> <script> export default { name: "AboutUs", props: { dialogProps: { close: Function, openElse: Function } } } </script> <style scoped> .container { overflow: hidden; width: 200px; height: 130px; text-align: center; } .open-else-margin { margin-left: 10px; } </style>Copy the code
-
ContactUs
<template> <div class="container"> <h1> Contact Us </h1> <div> <button @click="dialogProps.back"> back </button> </div> </div> </template> <script> export default { name: "ContactUs", props: { dialogProps: { back: Function } } } </script> <style scoped> .container { overflow: hidden; width: 300px; height: 170px; text-align: center; } </style> Copy the code
Create a Dialog Component
React can be mounted on the body node via createPortal. Vue also provides a teleport with similar functionality, but v-if is not as flexible as native js when matching Dialog sub-components. Finally, you need to introduce the Dialog component in app.vue as a container for the Dialog child components to display.
<template> <! > <teleport to="body"> <section class="overlay" v-for="({dialogFlag, dialogProps}) in dialogStack" :key="dialogFlag"> <main class="wrapper"> <AboutUs v-if="dialogFlag === 'About_Us'" :dialog-props="dialogProps" /> <ContactUs v-if="dialogFlag === 'Contact_Us'" :dialog-props="dialogProps" /> </main> </section> </teleport> </template> <script lang="ts"> import {inject} from 'vue'; import AboutUs from '@/components/Dialog/AboutUs.vue'; import ContactUs from '@/components/Dialog/ContactUs.vue'; export default { name: 'index', components: {ContactUs, AboutUs}, setup() { // @ts-ignore const {dialogStack} = inject('DialogContext'); return { dialogStack }; }}; </script> <style scoped> .overlay { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, .5); z-index: 9999; } .wrapper { border-radius: 20px; box-shadow: 0 2px 30px 0 rgba(0, 0, 0, .4); background-color: #fff; } </style>Copy the code
use
Use inject to get data from useDialog and call through Dispatch. The following is the specific code:
<template> <button @click="openDialog"> dispatch dialog </button> <! --> <div class="block"/> </template> <script lang="ts"> import {inject} from 'vue'; export default { name: 'Demo', setup() { // @ts-ignore const {dispatch} = inject('DialogContext'); const close = () => { dispatch({ type: 'close' }); }; const back = () => { dispatch({ type: 'back' }); }; const openElse = () => { dispatch({ type: 'open', dialogFlag: 'Contact_Us', dialogProps: { back } }); }; const openDialog = () => { dispatch({ type: 'open', dialogFlag: 'About_Us', dialogProps: { close, openElse } }); }; return { openDialog }; }}; </script> <style scoped> .block { width: 200px; height: 1500px; background-color: #2b2b2b; } </style>Copy the code
Finally, a look at the effects:
Welcome to the comments section.