my-blog:https://tuimao233.gitee.io/ma...
Vue3 优雅的模态框封装办法 - 初探
Vue3 优雅的模态框封装办法 - 实际
通过前篇文章的介绍,大家曾经理解了虚构节点和瞬移组件,接下来咱们利用虚构节点与瞬移组件,封装一个模态框组件。
首先,得先明确咱们指标,就是咱们想要做进去的成果,就是兼容两个形式调用的模态框组件
第一种通过 template 间接应用:
<model v-model="show" title="题目" @confirm="onConfirm" @clone="onClone"> 我是模态框文字 </model>
第二种是间接 JavaScript 调起:
Modal({title: '题目', content: '我是模态框文字'}) .then(()=> { }) .catch(()=> { })
从这两段形式能够看出,无论是通过 Modal
,还是通过<model>..</model>
,可传入参数都保持一致,由此可见,组件调用形式传参统一,所以咱们首先新建components/Modal/props.ts
,在内部定义 props
参数类型:
/** 模态框固定 props 参数, 用于调用模态框胜利|敞开|销毁 */export const modalProps = { // 是否展现组件 modelValue: Boolean, // 组件隐没时(移除实例) vanish: Function, // 组件调用胜利事件 resolve: Function, // 组件调用失败事件 reject: Function}/** 组件内传入 props 参数, 用于模态框自定义性能 */export const componentProps = { // 模态框题目 title: String, // 模态框内容 content: String}/** 组件内所有 Props 参数, 合并参数 */export const props = {...modalProps, ...componentProps}
这一步实现之后,咱们在创立components/Modal/index.vue
,导入 props
类型:
<template> <div></div></template><script lang="ts">import { defineComponent } from 'vue'import { props } from './props'export default defineComponent({ props})</script><style lang="scss" scoped></style>
到这一步后,咱们在定义一个通过js代码渲染组件的办法:
// components/Modal/utils.vueimport { Component, h, render } from "vue"/** * 渲染组件实例 * @param Constructor 组件 * @param props 组件参数 * @returns 组件实例 */export const renderInstance = (Constructor: Component, props: Record<string, any>) => { // 创立组件容器, 这一步是必须的, 在销毁组件时会应用到 const container = document.createElement('div') // 在 props 增加组件隐没钩子, 移除以后实例, 将销毁办法提供给组件 // 这里不须要调用 document.body.removeChild(container.firstElementChild) // 因为调用 render(null, container) 为咱们实现了这项工作 props.vanish = () => { render(null, container) } // 创立虚构节点, 渲染组件 const vnode = h(Constructor, props) render(vnode, container) // 增加子元素(组件)至父元素 document.body.appendChild(container.firstElementChild)}
渲染办法定义实现后,咱们就能够先把通过 js 调起的办法给做了:
import { ExtractPropTypes, ref } from "vue"import Index from './index.vue'import { componentProps } from './props'import { renderInstance } from "./utils"/** 组件 Props 类型, ExtractPropTypes 可将 Constructor 转换为对应值类型 */type Props = ExtractPropTypes<typeof componentProps>/** 组件调用 resolve 返回后果 */type Result = { path: string }[]/** * 模态框调用办法 * @param props * @returns {Promise} */export const Modal = (props: Props) => { return new Promise<Result>((resolve, reject) => { renderInstance(Index, { // 这里 modelValue, 为了使组件可批改, 须要传入 ref // 留神这块中央,咱们将这个值设置为 true 为了调起即间接展现组件 modelValue: ref(true), ...props, resolve, reject }) })}
这里须要留神的是,通过 h
函数创立的实例,其 props
在组件中,无奈通过 emit
批改,批改会生效,所以为了解决这个问题,须要在调起办法传入 modelValue Ref
。
接下来咱们进行欠缺components/Modal/index.vue
组件的模态框逻辑:
<template> <teleport to="body"> <!-- after-leave 组件动画完结时, 调用销毁组件(如果有的话) --> <transition name="fade" @after-leave="vanish"> <div class="base-model__mask" v-show="show"> <div class="base-model__content"> <div class="base-model__title">{{ title }}</div> <!-- 插入自定义插槽, 这里判断默认插槽有没有应用 --> <!-- 如果应用, 则渲染插槽, 如果没有, 则渲染 content --> <slot v-if="$slots['default']" /> <template v-else>{{ content }}</template> <div class="base-model__control"> <span @click="onConfirm">确定</span> <span @click="onClone">敞开</span> </div> </div> </div> </transition> </teleport></template><script lang="ts">import { defineComponent, computed, isRef, nextTick, watch } from 'vue'import { props } from './props'export default defineComponent({ props, setup: (props, { emit }) => { // 组件显示的数据双向代理 const modelValue = computed({ get: () => <boolean>props.modelValue, set: () => emit('update:modelValue') }) // Modal 办法调用传入 props 无奈通过 emit 批改 // 所以如果传入间接是一个 ref 则间接应用 const show = isRef(props.modelValue) ? props.modelValue : modelValue // 如果初始化为 true , 切换状态让动画失常显示 if (show.value) { show.value = false nextTick(() => show.value = true) } // 敞开事件, 调用 reject, 为了兼容模板上间接应用组件, 还要在调用一次 clone 事件 const onClone = () => { props.reject?.() emit('clone') show.value = false } // 确定事件, 调用 resolve, 为了兼容模板上间接应用组件, 还要在调用一次 confirm 事件 const onConfirm = () => { props.resolve?.() emit('confirm') show.value = false } return { show, onConfirm, onClone } }})</script><style lang="scss" scoped>.base-model__mask { position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.4);}.base-model__content { position: absolute; border-radius: 20px; width: 600px; height: 300px; background-color: #ffffff; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px;}.base-model__control { position: absolute; right: 0; bottom: 20px; span { margin-right: 20px; }}/* 组件动画 start */.fade-enter-active,.fade-leave-active { transition: opacity 0.2s;}.fade-enter-from,.fade-leave-to { opacity: 0;}.fade-enter-top,.fade-leave-from { opacity: 1;}/* 组件动画 end */</style>
到了这里,咱们能够测试一下组件调用是否失常,例如,咱们通过应用 template
组件形式调用:
<template> <img alt="Vue logo" src="./assets/logo.png" @click="show = true" /> <modal @clone="onClone" @confirm="onConfirm" v-model="show" title="我是题目" > 啦啦啦我是自定义内容 </modal></template><script lang="ts">import { defineComponent, ref } from 'vue'import Modal from './components/Modal/index.vue';export default defineComponent({ components: { Modal }, setup: () => { const show = ref(false) const onClone = () => { console.log('模态框点击敞开') } const onConfirm = () => { console.log('模态框点击确认') } return { onClone, onConfirm, show } }})</script>
在测试一下,通过 JavaScript 调用模态框:
<template> <img alt="Vue logo" src="./assets/logo.png" @click="onClick" /></template><script lang="ts">import { defineComponent, ref } from 'vue'import { Modal } from './components/Modal';export default defineComponent({ components: { }, setup: () => { const onClick = () => { Modal({title: '我是题目~~~', content: '我是内容~~~'}) .then(() => { console.log('组件调用胜利') }) .catch(() => { console.log('组件调用失败') }) } return {onClick} }})</script>
到这里,整个模态框的根本逻辑都组成了,在这根底下,就可基于需要下欠缺模态框与定制内容,也可通过该办法,二次封装 el-dialog
组件,只须要将 components/Modal/index.vue
的逻辑批改一下即可,下一篇文章,咱们在这根底下在进行欠缺,使得组件能齐全胜任业务需要。