slot(卡槽) ,和 el-dialog 原理类似,只是再封装了一层,少定义了 normalDialog.vue 文件。 缺点:调用复杂,不灵活;不容易控制关闭的流程;只能在 template 中定义 。
component(动态组件) ,创建 commonDialog.vue ,统一挂在 App.vue 下,利用 <component :is="componentId"></component> 动态切换弹框主体, commonDialog.vue 监听 componentId 变化来切换弹框主体。 缺点:要提前将所有弹框主体组件注册到commonDialog.vue页面的components上;依赖于vuex,侵入性较强;纯js文件通过vuex弹出弹框相对复杂,不灵活 。
重写 render , render 是 Vue 对造轮子开发者开放的后门。动态弹框可作为独立的功能模块,内部通过new Vue ,重写 render 控制渲染内容。 独立 Vue 实例,可预先创建,可在任何位置控制弹框,灵活,清晰 。 缺点:暂无
1. 整体代码
先整体预览一下代码,下面再细分讲解。
import Vue from 'vue' import merge from 'lodash/merge' import orderBy from 'lodash/orderBy' // 按钮配置项构造器 function btnBuilder(options) { const defaultBtn = { text: '按钮', // 显示文本 clickFn: null, // 点击回调 type: 'default', // 样式 isHide: false, // 是否隐藏 order: 2 // 顺序 } return { ...defaultBtn, ...options } } export default class JSDialog { constructor(originOptions) { this.options = {} this.vm = null this._mergeOptions(originOptions) this._initVm() } // 参数合并 _mergeOptions(originOptions) { const defaultOptions = { component: '', // 弹框主体vue页面 // 可扩展el-dialog官方api所有配置项,小驼峰aaaBbbCcc dialogOpts: { width: '40%', title: '默认标题' }, // 传入弹框主体vue组件的参数 props: {}, // 点击确定回调 onOK: () => { console.log('JSDialog default OK'), this.close() }, // 点击取消回调 onCancel: () => { console.log('JSDialog default cancel'), this.close() }, footer: { ok: btnBuilder({ text: '确定', type: 'primary', order: 0 }), cancel: btnBuilder({ text: '取消', order: 1 }) } } // 参数合并到this.options merge(this.options, defaultOptions, originOptions) const footer = this.options.footer Object.entries(footer).forEach(([key, btnOptions]) => { // 确定和取消默认按钮 if (['ok', 'cancel'].includes(key)) { const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel // 默认按钮回调优先级: footer配置的clickFn > options配置的onOK和onCancel btnOptions.clickFn = btnOptions.clickFn || clickFn } else { // 新增按钮 // 完善配置 footer[key] = btnBuilder(btnOptions) } }) } _initVm() { const options = this.options const beforeClose = this.options.footer.cancel.clickFn // 弹框右上角关闭按钮回调 this.vm = new Vue({ data() { return { // 需要响应式的数据 footer: options.footer, // 底部按钮 visible: false // 弹框显示及关闭 } }, methods: { show() { // 弹框显示 this.visible = true }, close() { // 弹框关闭 this.visible = false }, clearVm() { // 清除vm实例 this.$destroy() } }, mounted() { // 挂载到body上 document.body.appendChild(this.$el) }, destroyed() { // 从body上移除 document.body.removeChild(this.$el) }, render(createElement) { // 弹框主体 const inner = createElement(options.component, { props: options.props, // 传递参数 ref: 'inner' // 引用 }) // 控制按钮显示隐藏 const showBtns = Object.values(this.footer).filter(btn => !btn.isHide) // 控制按钮顺序 const sortBtns = orderBy(showBtns, ['order'], ['desc']) // 底部按钮 jsx 写法 const footer = ( <div slot="footer"> {sortBtns.map(btn => ( <el-button type={btn.type} onClick={btn.clickFn}> {btn.text} </el-button> ))} </div> ) // 弹框主体 const elDialog = createElement( 'el-dialog', { // el-dialog 配置项 props: { ...options.dialogOpts, visible: this.visible, beforeClose }, // **** 看这里,visible置为false后,el-dialog销毁后回调 ***** on: { closed: this.clearVm }, ref: 'elDialog' }, // 弹框内容:弹框主体和按钮 [inner, footer] ) return elDialog } }).$mount() } // 封装API // 关闭弹框 close() { this.vm.close() } // 显示弹框 show() { this.vm.show() } // 获取弹框主体实例,可访问实例上的方法 getInner() { return this.vm.$refs.inner } }
2. 参数合并