共计 5215 个字符,预计需要花费 14 分钟才能阅读完成。
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.vue
import {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
的逻辑批改一下即可,下一篇文章,咱们在这根底下在进行欠缺,使得组件能齐全胜任业务需要。