背景:
公司的前端技术框架向前看,Vue2.x ---> Vue3.0。业务方希望保留原有的UI
准备工作
第三方组件注册 - XDialog
- 组件注入Vue实例时 必须要有 install 方法 (也就是在根组件创建时,要进行一下use)
// main.js
import { createApp } from 'vue'
...
import XDialog from '@/components/XDialog/index'
createApp(App)
.use(XDialog) // 注入组件
.use(...)
// @/components/XDialog/index.js
... 此处先省略个 20几行
// 导出时,给出 install 方法
export default {
install: (App) => {
// 为什么使用 provide / inject 的方式去做呢<尤雨溪推荐的,你问他呗>
App.provide('XDialong', MyDialog)
// 当然也可以使用下面的方式 (还是告诉你吧:因为我希望在script中进行调用,不写在template里)
// App.component(XDialog.name, XDialog) 注意避坑 -> 组件内部一定要写name属性
}
}
- 组件调用
<script lang="ts">
import { ...., inject } from 'vue';
export default defineComponent({
setup() {
// 这里的类型推断我一直不会整,希望有大佬指点一下 🤷♀️🤷♀️
const $Dialog: Function = inject('XDialog') || noop;
const addSomethins = () => {
// 注意观察一下,前面几个参数都是和Element-plus中的dialog参数保持一致的
// 更多的自定义配置请继续往下看啊🤣
$Dialog({
title: '新 增',
width: '500px',
closeOnClickModal: false,
contentComponent: defineAsyncComponent(() => import('./Form.vue')),
confirm: (component: Component) => {
// 这里的component是异步组件的一个引用,方便获取组件参数
console.log(component)
// 如果在此函数中返回一个false 将不会让弹窗关闭
},
cancel: () => {}
})
}
}
})
</script>
- 组件二次封装的核心实现 🤞🤞🤞🤞
特别说明一下这里的实现注册完全是看了 element-plus 的组件注册去实现的,相当于拿了别人的东西,再自己装牛*。 面对疾风吧。
// @/components/XDialog/index.js
// 导入 composition-api 为什么有compsition-api和为什么要这么设计 请自行百度!
import { h, render } from 'vue'
import XDialog from 'XDialog.vue'
const getContainer = () => document.createElement('div')
const initInstance = (props, container) => {
const vnode = h(XDialog, props) // 创建 VNode 虚拟dom
render(vnode, container) // 渲染到container中 此时并没有挂到body
const $el = container.firstElementChild
document.body.appendChild($el) // 加入到body
// 返回当前的 el 以及实例
return {
$el,
instance: vnode.component
}
}
const show = (options) => {
const container = getContainer()
const { instance, $el } = initInstance(options, container)
const vm = instance.proxy // 这儿的vm是 instance 的 proxy -- h 生成时是这样设计的(我也不知这样解释是不是完全正确,源码没认真读...)
for (const prop in options) {
// 遍历传递 props (不能将组件内部的私有属性覆盖了)
if (options.hasOwnPrroperty(prop) && !vm.$props.hasOwnProperty(prop)) {
vm[prop] = options[prop]
}
}
vm.visible = true // 这里的visible是控制dialog显示隐藏的字段-也支持自定义
return {
$el,
vm,
instance
}
}
function MyDialog (options) {
const context = show({
...options,
// 这里的 closeCallback 是在内层当dialog关闭时,将当前的 dialog 以及其父级div进行移除
closeCallback() {
context.vm.visible = false
setTimeout(() => {
document.body.removeChild(context.$el)
}, 150)
}
})
}
// 导出时,给出 install 方法
export default {
install: (App) => {
// 为什么使用 provide / inject 的方式去做呢<尤雨溪推荐的,你问他呗>
App.provide('XDialong', MyDialog)
// 当然也可以使用下面的方式 (还是告诉你吧:因为我希望在script中进行调用,不写在template里)
// App.component(XDialog.name, XDialog) 注意避坑 -> 组件内部一定要写name属性
}
}
- XDialog.vue关键函数
- close 函数
setup(props) {
...
const close = () => props.colseCallback && props.colseCallback()
}
- confirm 函数
setup(props) {
...
// 确认函数的执行以及组件实例的暴露
const confirmHandler = async () => {
if (typeof props.confirm !== 'function' ||
(await props.confirm(dialogRef.value) !== false)
) {
close()
}
}
}
第三方组件注册 - XConfirm
- 组件注册以及组件调用与XDialog都是大同小异的 也是基于el-dialog组件进行改造
唯独在组件的 show 方法做了手脚
- 直接给出相关的代码
// @/components/XConfirm/index
...
async function MyConfirm (options) {
let ctx
// 为什么只有用到resolve, 因为不想外层的链式调用还写一个catch
return new Promise(resolve => {
ctx = show({
...options,
confirm() {
resolve(true)
},
cancel() {
resolve(false)
},
closeCallback() {
ctx.vm.visible = false
setTimeout(() => {
document.body.removeChild(ctx.$el)
}, 150)
}
})
})
}
export default {
install (App) {
App.provide('XConfirm', MyConfirm)
}
}
// 业务代码调用时
const $confirm = inject('XConfirm')
const doSomthing = () => {
$confirm({
title: '提 示',
message: '确认今晚吃鸡吗?',
width: '400px',
top: '20vh',
closeOnClickModal: false
}).then(async (bool) => {
if (bool) {
.....Do your shit
}
})
}
第三方组件注册 - XMessage
- 直接上码吧 --- 你可以自己拓展了
// @/components/XMessage/index
....
function MyMessage (options) {
const delay = options.delay || 2000
const ctx = show({ ...options })
setTimeout(() => {
ctx.vm.visible = false
setTimeout(() => {
document.body.removeChild(ctx.$el)
}, 150)
}, delay)
}
export default {
install(App) {
App.provide('XMessage', MyMessage)
}
}