使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)
在如今日益繁琐的业务场景中,我们经常被重复的 Model 类定义工作所困扰,这意味着一旦遇到了 Model 类组件,我们需要不断重复定义 cancel
、confirm
、visible
等通用字段, 在当保存组件状态时,经常的需要对 Model 流程进行控制(open model
-> edit data
-> @confirm
-> save data
-> clear data
) 这极大的加剧了工作量,并在组件重复使用时产生大量的冗余代码。
Unoverlays 是构建弹出层的统一插件,创建回调(命令式)方法、以及在 Vue Template 或 React Jsx 中(声明式)使用,并具有以下特点。
- 制作类似于
element-plus/antd
... 的 Message 或 Dialog - 同时支持回调(命令式)与Template/JSX(声明式)
- 使用现有组件库(如 element-plus、antd)集成和定制化功能
- 支持组件继承全局应用上下文
- Unoverlays 支持
vue2|3
、react
等前端渐进式框架。 - 更稳定!单元测试覆盖率 99.54% (Vue)
这篇文章主要以 Vue 为主,使用 react 的小伙伴可以在 @unoverlays/react 文档中查看使用方法。
Devtools
由 Unified Overlays 创建的组件,均支持对应框架的 Devtools(React、Vue)
Supported
React Developer Tools | Vue.js Devtools |
---|---|
✅(holder) | ✅(holder、child-app) |
holder
在对应的组件中插入持有者,使其在虚拟 DOM 当中。child-app
创建独立的应用,由 devtools 识别新应用。
超快速开始
使用 unoverlays
提供的 useOverlayMeta
Hook 创建弹出层组件(Vue、React)
ts
import { useOverlayMeta } from '@unoverlays/vue'
// or
import { useOverlayMeta } from '@unoverlays/react'
// 在你的 Vue、React 弹出层组件中,使用 useOverlayMeta 获取弹出层元信息
const { visible, confirm, cancel } = useOverlayMeta({
// 弹出层动画的持续时间, 可以避免组件过早被销毁
animation: 1000
})
// 成功并关闭弹出层
confirm('ok')
// 失败并关闭弹出层
cancel('nook')
// 用于控制显示隐藏
visible
使用 createOverlay|renderOverlay
转换为命令式回调(callback)
const callback = createOverlay(OverlayComponent)
const result = await callback(props)
const result = renderOverlay(OverlayComponent, {
props: { msg: 'ok' }
})
定制化(使用 element-plus dialog)
@unoverlays/vue 还可以使用第三方的组件库进行二次封装,下面是一个使用 element-plus(dialog)的例子。
以element-plus(dialog)为例(其他组件库同理)
<!-- overlay.vue -->
<script setup>
import { defineEmits, defineProps } from 'vue-demi'
import { useOverlayMeta } from '@unoverlays/vue'
const props = defineProps({
title: String,
})
const { visible, confirm, cancel } = useOverlayMeta({
animation: 1000
})
</script>
<template>
<el-dialog v-model="visible" :title="title" @close="cancel()">
<!-- 你的定制化内容 -->
<button @click="confirm(`${title}:confirmed`)" />
</el-dialog>
</template>
import { createOverlay } from '@unoverlays/vue'
import OverlayComponent from './overlay.vue'
const callback = createOverlay(OverlayComponent)
const value = await callback({ title: 'myElDialog' })
// value === "myElDialog:confirmed"
组件调试
在使用 createOverlay
时会创建一个 Vue 子应用,这个应用区别与主应用,可在 Vue devtools 中查看与调试组件。
你也可以使用 useInjectHolder 在组件内部创建弹出层,并继承应用的当前上下文。
生成根元素的 ID 与应用名称使用同一 ID,在创建组件时,会自动对 ID 进行 PascalCase
处理。
可以通过渲染选项,更改生成 ID 名称。
const callback = createOverlay(OverlayComponent)
callback({}, {
id: 'custom-overlay',
// 关闭 ID 后续自增长
autoIncrement: false
})
继承上下文
如果你全局注册了 Unoverlays,它会自动继承你的应用上下文,你也可以通过更细致的控制来传入上下文。
import { getCurrentInstance } from 'vue-demi'
import Component from './overlay.vue'
// 在你的 setup 中
const { appContext } = getCurrentInstance()!
renderOverlay(Component, {
props: {},
appContext
})
外部控制
如果把控制权都交给 Component,在一些使用场景时会受到到限制,Unoverlays 转换的组件允许用户在外部控制组件的流程
Model
的返回值的功能不仅仅包括 Promise
在此基础还有 confirm
和 cancel
const Model = createOverlay(MyComponent)
const instance = Model({/* you props */})
function close() {
instance.cancel('no')
instance.catch((value) => {
// log: no
console.log(value)
})
}
function yes() {
instance.confirm('yes')
instance.then((value) => {
// log: yes
console.log(value)
})
}
模版支持
使用 @unoverlays/vue
创建的组件,除了支持使用命令式(Imperative)方法调用外,还支持在 <template>
中使用。
支持了 <template>
中使用的组件,同样也支持使用 callback 调用,并不会影响彼此功能,这是一项可选项。
步骤.1: Define Component #
在 <template>
中使用,需要显式定义 modal
与 event
。
<!-- overlay.vue -->
<script setup>
import { defineEmits, defineProps } from 'vue-demi'
import { useOverlayMeta } from '@unoverlays/vue'
const props = defineProps({
title: String,
// 在 Template 中使用,需要定义 v-modal 所使用的字段(默认对应 visible)
visible: Boolean
})
// 定义组件中使用的事件类型(默认:cancel、confirm)
defineEmits(['cancel', 'confirm'])
const { visible, confirm, cancel } = useOverlayMeta({
// 如果使用 template 渲染,animation 则可以不需要定义
// animation: 1000,
})
</script>
如果您想替换为其他的字段与事件名,可以通过 useOverlayMeta
传入对应的配置实现。
<!-- overlay.vue -->
<script setup>
import { defineEmits, defineProps } from 'vue-demi'
import { useOverlayMeta } from '@unoverlays/vue'
const props = defineProps({
title: String,
modalValue: Boolean
})
defineEmits(['nook', 'ok'])
const { visible, confirm, cancel } = useOverlayMeta({
event: { confirm: 'ok', cancel: 'nook' },
modal: 'modalValue',
})
</script>
<template>
...
</template>
步骤.2: In Template #
定义 modal
与 event
后,即可在 <template>
中使用弹出层组件。
<!-- overlay.vue -->
<script setup>
import OverlayComponent from './overlay.vue'
const visible = ref(false)
const confirm = () => {
// ...
}
const cancel = () => {
// ...
}
</script>
<template>
<OverlayComponent
v-model:visible="visible"
@confirm="confirm"
@cancel="cancel"
/>
</template>
template-promise
vue-template-promise 是 Anthony Fu 创建的一个项目,它一样可以解决在 template 中使用模态框打断编程流程的问题,它可以很好的与 @unoverlays/vue 配合使用。
<script setup lang="ts">
import OverlayComponent from './overlay.vue'
const TemplatePromise = useTemplatePromise<ReturnType>({
transition: {
name: 'fade',
appear: true,
},
})
</script>
<template>
<TemplatePromise v-slot="{ resolve, reject }">
<overlay-component visible @confirm="resolve" @cancel="reject">
<!-- 组件中的自定义插槽 -->
</overlay-component>
</TemplatePromise>
</template>
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
Injection Holder
除了使用 createOverlay
与 renderOverlay
创建使用弹出层组件外,还支持使用 useInjectHolder
创建在组件内部的弹出层组件,并继承应用的当前上下文。
<!-- App.vue -->
<script setup>
import { useInjectHolder } from '@unoverlays/vue'
import OverlayComponent from './overlay.vue'
// 通过 useInjectHolder(Component) 创建支持当前 context 的组件持有者
const [overlayApi, holder] = useInjectHolder(OverlayComponent)
function open() {
// 打开弹出层
overlayApi()
.then((result) => {})
}
</script>
<template>
<div @click="overlayApi">
open
</div>
<!-- 使用 <component :is="holder" /> 挂载 -->
<component :is="holder" />
</template>
插槽与 VNode 渲染
如果您想支持 template 模式下渲染插槽,以及 props 中传入的某个字段,只需要定义插槽后传入默认内容。
<script setup>
import { useOverlayMeta } from '@unoverlays/vue'
defineProps({ title: String })
const { visible, /* ... */ } = useOverlayMeta()
</script>
<template>
<div v-if="visible">
<slot name="title">
<!-- 传入默认内容 -->
{{ title }}
</slot>
</div>
</template>
如果您想在回调(命令式)下,支持某个字段渲染 VNode,建议您使用内置组件 FieldRender
。
FieldRender
会帮您处理 VNode、Component、String 的渲染,它可以在回调和 template
下同时使用。
以下是同时支持 Slots、String、VNode、Component 的完整例子:
<script lang="ts" setup>
import { Component, VNode } from 'vue'
import { FieldRender, useOverlayMeta } from '@unoverlays/vue'
defineProps<{
title?: String | VNode | Component
}>()
const { visible, /* ... */ } = useOverlayMeta()
</script>
<template>
<div v-if="visible">
<slot name="title">
<!-- 传入字符串、虚拟节点或组件 -->
<FieldRender :value="title" />
</slot>
</div>
</template>
const result = await renderOverlay(Component, {
props: {
title: h('div', 'You Content')
}
})
转载自:https://juejin.cn/post/7213556846666432573