likes
comments
collection
share

使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)

作者站长头像
站长
· 阅读数 66

在如今日益繁琐的业务场景中,我们经常被重复的 Model 类定义工作所困扰,这意味着一旦遇到了 Model 类组件,我们需要不断重复定义 cancelconfirmvisible 等通用字段, 在当保存组件状态时,经常的需要对 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|3react 等前端渐进式框架。
  • 更稳定!单元测试覆盖率 99.54% (Vue)

这篇文章主要以 Vue 为主,使用 react 的小伙伴可以在 @unoverlays/react 文档中查看使用方法。

Devtools

由 Unified Overlays 创建的组件,均支持对应框架的 Devtools(React、Vue)

Supported
React Developer ToolsVue.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 在组件内部创建弹出层,并继承应用的当前上下文。

使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)

使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)

生成根元素的 ID 与应用名称使用同一 ID,在创建组件时,会自动对 ID 进行 PascalCase 处理。

可以通过渲染选项,更改生成 ID 名称。

const callback = createOverlay(OverlayComponent)
callback({}, {
  id: 'custom-overlay',
  // 关闭 ID 后续自增长
  autoIncrement: false
})

使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)

使用 Vue Render 创建回调(命令式)模态框(Unified Overlays)

继承上下文

如果你全局注册了 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
评论
请登录