vue-layerx 1.0.1 正式发布。
文档:xuyimingwork.github.io/vue-layerx/
仓库:github.com/xuyimingwor…
业务页弹窗一多,模板里往往会出现这种风景:
- 一堆
xxxVisible/v-model样板代码 - Dialog / Drawer 和表单绑死在同一个文件,内容复用很麻烦
- 想命令式
open(),不想换掉现有组件库、又不想写 JSX
如果你也踩过这些坑,这篇可以当作一次「能落地」的解法介绍。
先看结果:三行代码打开弹窗
const useDialog = createLayer(ElDialog)
const dialog = useDialog(HelloWorld)
dialog.open()
拆开读就是三件事:
- 适配:用项目里已有的 Dialog / Drawer / Popup 生成
useDialog - 绑定:传入普通业务内容组件
- 打开:命令式调用,页面上不必再堆弹层模板和
visible
vue-layerx 的立场很明确:
不碰的组件库,只提供命令式 API,把「弹层壳」和「业务内容」拆开编排。
核心模型:容器 × 内容
一次弹层只有两个角色:
| 职责 | 例子 | |
|---|---|---|
| 容器(container) | 显隐、遮罩、标题栏等壳 | ElDialog、ElDrawer、项目内 BaseDialog |
| 内容(content) | 普通业务组件,可页内复用 | UserForm、详情面板 |
对应 API 流水线:
- createLayer(容器) => 配容器
- useDialog(内容) => 配内容
- dialog.open() => 单次打开
这种拆法带来新的治理空间:
- 容器组件可以在基建层统一(宽度、遮罩、关闭策略)
- 内容组件可以在业务层复用(弹窗内、页面内共用同一份组件)
- 调用页只关心「打开谁、传什么参」
从适配到打开:最小可用路径
1. 适配已有容器
// dialog.ts
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'
export const useDialog = createLayer(ElDialog, {
props: {
// 配置一些默认属性
width: '480px',
destroyOnClose: true,
appendToBody: true,
}
})
Element Plus、Ant Design Vue、Vant、项目内 BaseDialog 都可以——只要是靠 v-model(或同类显隐 prop)控制显示隐藏的组件。
2. 内容就是普通组件
<!-- HelloWorld.vue -->
<template>
<p>Hello World</p>
</template>
3. 使用时命令式打开
<script setup lang="ts">
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'
const dialog = useDialog(HelloWorld)
</script>
<template>
<button @click="dialog.open({ props: {
// 这里可以向 content 传递参数
message: `你好`
} })">打开弹层</button>
</template>
页面模板里不再出现 Dialog 样板;visible 变量也不用维护。
插槽继续用模板
命令式组件的问题是,将组件交给工具渲染后,模板上没有组件了,也没有地方插入插槽了。
大部分方案让你传 JSX 节点,但是这会带来项目复杂度上升。
在 vue-layerx 中,你可以利用 LayerTemplate:
<script setup lang="ts">
import { LayerTemplate } from 'vue-layerx'
import HelloWorld from './HelloWorld.vue'
import { useDialog } from './dialog'
const dialog = useDialog(HelloWorld)
</script>
<template>
<button @click="dialog.open()">打开弹层</button>
<LayerTemplate :to="dialog" name="footer" container>
我会出现在 dialog 的 footer slot 里
</LayerTemplate>
</template>
不强制 JSX,团队学习成本更低。
内容组件里的也能传递模板
每次都让调用方去传递 footer 内容有点麻烦,而且业务逻辑没有自包含。
vue-layerx 还提供了 defineLayer,让内容组件也可以向容器传递配置或插槽模板。
<!-- HelloWorld.vue -->
<script setup lang="ts">
import { defineLayer, LayerTemplate } from 'vue-layerx'
const layer = defineLayer({
// 从内容组件配置 dialog 容器
props: { width: '380px' }
})
</script>
<template>
<p>Hello World</p>
<LayerTemplate :to="layer" name="footer">
我会出现在 dialog 的 footer slot 里
</LayerTemplate>
</template>
问:这样写和直接写 HelloWorldDialog.vue 有什么区别?
答:区别是 HelloWorld.vue 和 ElDialog.vue 拆开了。HelloWorld.vue 可以作为普通组件随便用在某个页面上,同时可以和 ElDrawer.vue 等其它容器组件随意组合。
对于项目中已有的
HelloWorldDialog.vue,vue-layerx 提供了渐进式接入方案! 见:容器与内容未拆分
企业向:配置可叠,也能「最后钉死」
真实项目里,同一项配置会出现在多处:
createLayer:基建默认defineLayer:内容契约useDialog/open:调用方覆盖
vue-layerx 允许它们叠加,并约定越靠后的优先级越高。
但有时项目要的不是「默认值」,而是「合并完仍然必须如此」——例如全站禁止点遮罩关闭。
这时用 adapter:等各处配置合并完,再改一次结果。
import type { LayerAdapter } from 'vue-layerx'
import { createLayer } from 'vue-layerx'
import { ElDialog } from 'element-plus'
const enforceMaskNotClosable: LayerAdapter = (config) => ({
...config,
container: {
...config.container,
props: {
...config.container?.props,
closeOnClickModal: false,
},
},
})
export const useDialog = createLayer(ElDialog, {
adapter: enforceMaskNotClosable,
})
是的,vue-layerx 提供了 aop 能力
其它能力
- 提供等待弹窗结果的能力:
await dialog.confirm() - 响应式配置的能力:
useDialog(HelloWorld, () => ({ props: { id: props.id } })) - 命令式调用下
Provide / Inject的上下文断裂与修复 LayerTemplate跨实例插槽传送时的响应式依赖追踪- 配置合并(基建/内容/调用方/AOP)在高并发场景下的优先级冲突
- vue devtools 内容展示
适合谁 / 不适合谁
更适合:
- Vue 3 业务中台,弹窗 / 抽屉很多
- 已有 Element / Ant / Vant / 自研壳,不想换库
- 希望表单组件既能页内用,又能命令式打开
- 需要基建层统一弹层策略(关闭规则、宽度、字段映射)
暂时不必:
- 全项目只有一两个简单 Dialog,样板还能接受
- 团队已经深度绑定某套「自研 Modal 运行时」,迁移成本大于收益
质量保证
- 全量 TypeScript
- 零运行时外部依赖(peer 只要求 Vue)
- 语句 / 分支 / 函数 / 行测试覆盖 100%,测试用例 300+
- 支持 SSR(可在 Nuxt / Vite SSR 中安全引入)
- 文档站 + Playground,可直接点着试
我还是 vue-asyncx 的作者
vue-asyncx 解决的是异步样板代码。
这次的 vue-layerx 解决的是弹层样板——同样是组合式、少侵入、尽量复用你已有的代码。
现在就可以试
pnpm add vue-layerx
文档网站里有许多可以直接点击的案例哟,欢迎体验