先说说封装 dialog 的普通写法
参考el-dialog
,使用 .sync
修饰符 + $emit('update:xx')
实现双向绑定 visible
,并提供默认插槽
<dynamic-dialog title="demo" :visible.sync="visible">
<my-form></my-form>
</dynamic-dialog>
// dynamic-dialog.vue
<template>
<el-dialog
:title="title"
:visible.sync="visible">
<slot></slot>
<div slot="footer">
<el-button @click="close">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'DynamicDialog',
props: {
title: String,
visible: Boolean
},
methods: {
confirm(){
this.$emit('confirm')
this.close()
},
close(){
this.$emit('update:visible', false)
},
}
}
</script>
手动实现 v-model
通过 computed
+ props
中的value
, 实现双向绑定 visible
<dynamic-dialog v-model="visible" title="demo">
<my-form></my-form>
</dynamic-dialog>
// dynamic-dialog.vue
... 省略重复代码
<script>
export default {
props: {
title: String,
value: Boolean
},
computed: {
visible: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val)
}
}
},
methods: {
close() {
this.visible = false
}
}
};
</script>
如果是业务组件 不用写插槽 直接封装成具体组件
// 选用户的弹框
<user-dialog
v-model="visible"
@close="cancel"
@confirm="userSelected" />
它何必是个组件? Vue.extend实现动态挂载
大家有没有想过,要用到 dialog
的地方,大部分场景是一个动作。比如选择一个用户,理论上只要执行
const user = selectUser()
把打开弹框,选择用户,关闭弹框的操作封装在 selectUser
方法里就行了。这样写可以使代码更加易读并且易维护。
要实现这个方法,首先了解一下 Vue.extend
。戳这里打开官网介绍
他返回一个类 Vue
的构造器,相当于它的子类,可以在合适的时机手动生成实例
dialog.js
import DynamicDialog from './DynamicDialog.vue'
import Vue from 'vue'
let instance = null;
const Dialog = Vue.extend(DynamicDialog);
Vue.prototype.$dialog = function(option) {
if (!instance) {
instance = new Dialog().$mount();
document.body.appendChild(instance.$el);
}
return instance.open(option);
};
DynamicDialog.vue
<template>
<el-dialog :title="title" :visible.sync="visible" v-bind="dialogOption">
<component :is="component" v-bind="props" @cancel="cancel" @confirm="confirm"></component>
</el-dialog>
</template>
<script>
export default {
data() {
return {
props: {},
title: '',
dialogOption: {},
component: null,
visible: true,
onClose: () => {},
onConfirm: () => {},
}
},
methods: {
open({ title, dialogOption, component, props, onClose, onConfirm }){
this.title = title
this.dialogOption = dialogOption
this.component = component
this.props = props
this.onClose = onClose
this.onConfirm = onConfirm
},
confirm(...args) {
this.close()
this.onConfirm(...args)
},
cancel() {
this.close()
this.onClose()
},
close() {
this.visible = false
}
},
};
</script>
如果需要选择名字中带“王”的用户,可以这么写
this.$dialog({
title: '选择用户',
dialogOption: { width: '600px' } // el-dialog 的属性
component: UserSelecter, // 动态组件
props: {
nameLike: '王' // UserSelector 组件的属性
},
onClose(){
console.log('取消选择')
},
onConfirm(user) {
console.log('选择了:', user)
}
})
避免回调地狱,使用 async/await
如何改进
以上写法虽然逻辑清晰,但是有几个问题:
- 使用了回调函数 层级太多,不好看,不好维护
- 如果在
this.$dialog()
方法下面还执行了某些操作,实际上是同步操作,对于新加入的小伙伴,很容易产生误导
使用 Promise
可以解决回调问题,使用async/await
是终极解决方案
// 改造 dialog
<script>
let resolve = null
export default {
data() {
return {
visible: true,
props: {},
title: '',
dialogOption: {},
component: null,
}
},
methods: {
open({ title, dialogOption, component, props }){
this.title = title
this.dialogOption = dialogOption
this.component = component
this.props = props
return new Promise((resolse, reject) => {
resolve = resolse
})
},
confirm(arg) {
this.close()
resolve(arg)
},
cancel() {
this.close()
resolve()
},
close() {
this.visible = false
}
},
};
</script>
在业务中使用:
const user = await this.$dialog({
title: '选择用户',
dialogOption: { width: '600px' }
component: UserSelecter,
props: { nameLike: '王' }
})
if(!user) return
console.log('选择了用户:', user)
到这里我们已经实现了el-dialog + Vue.extend + async/await 优雅地实现弹框操作
针对业务再次封装
回到最初的起点,封装选择用户的方法
// utils.js
function selectUser(option) {
return this.$dialog({
title: '选择用户',
dialogOption: { width: '600px' }
component: UserSelecter, // 动态组件
props: option
})
}
// 业务代码
const user = await selectUser({ nameLike: '王' })
if(!user) return
console.log('选择了用户:', user)