最近在使用Vuetify中VDataTable组件附带的VEditDialog小组件时,发现我在Dialog里的Input组件绑定了v-model,但是VEditDialog组件点取消时它能恢复原值,点击保存能保留修改后的值。
这看似挺简单的一个功能,但Vuetify却特意做了一个这样的小组件,我们就来看看它时怎么实现这个小功能的吧。
首先看到创建组件的第一行:
export default mixins(Returnable, Themeable)
它混入了Returnable
和Themeable
两个模块,其中Themeable
模块比较清楚,它是用于跟随框架主题切换和设置的通用模块。
然后再看VEditDialog的代码:
export default mixins(Returnable, Themeable).extend({
name: 'v-edit-dialog',
props: {
cancelText: {
default: 'Cancel',
},
large: Boolean,
eager: Boolean,
persistent: Boolean,
saveText: {
default: 'Save',
},
transition: {
type: String,
default: 'slide-x-reverse-transition',
},
},
data () {
return {
isActive: false,
}
},
watch: {
isActive (val) {
if (val) {
this.$emit('open')
setTimeout(this.focus, 50) // Give DOM time to paint
} else {
this.$emit('close')
}
},
},
methods: {
cancel () {
this.isActive = false
this.$emit('cancel')
},
focus () {
const input = (this.$refs.content as Element).querySelector('input')
input && input.focus()
},
genButton (fn: Function, text: VNodeChildren): VNode {
return this.$createElement(VBtn, {
props: {
text: true,
color: 'primary',
light: true,
},
on: { click: fn },
}, text)
},
genActions (): VNode {
return this.$createElement('div', {
class: 'v-small-dialog__actions',
}, [
this.genButton(this.cancel, this.cancelText),
this.genButton(() => {
this.save(this.returnValue)
this.$emit('save')
}, this.saveText),
])
},
genContent (): VNode {
return this.$createElement('div', {
staticClass: 'v-small-dialog__content',
on: {
keydown: (e: KeyboardEvent) => {
e.keyCode === keyCodes.esc && this.cancel()
if (e.keyCode === keyCodes.enter) {
this.save(this.returnValue)
this.$emit('save')
}
},
},
ref: 'content',
}, [this.$slots.input])
},
},
render (h): VNode {
return h(VMenu, {
staticClass: 'v-small-dialog',
class: this.themeClasses,
props: {
contentClass: 'v-small-dialog__menu-content',
transition: this.transition,
origin: 'top right',
right: true,
value: this.isActive,
closeOnClick: !this.persistent,
closeOnContentClick: false,
eager: this.eager,
light: this.light,
dark: this.dark,
},
on: {
input: (val: boolean) => (this.isActive = val),
},
scopedSlots: {
activator: ({ on }) => {
return h('div', {
staticClass: 'v-small-dialog__activator',
on,
}, [
h('span', {
staticClass: 'v-small-dialog__activator__content',
}, this.$slots.default),
])
},
},
}, [
this.genContent(),
this.large ? this.genActions() : null,
])
},
})
看它的操作逻辑:
- 开启对话框时自动找到对话框中的input并聚焦
- 取消则设置
isActive
为false
,上面有监听isActive
,变为true
时则打抛出“open”事件,操作打开对话框,延迟50毫秒自动聚焦输入框,如果为false
则抛出“close”事件关闭对话框
但是呢,却没看到它的保存方法,可能时再Returnable模块里写的,过去看看:
export default Vue.extend({
name: 'returnable',
props: {
returnValue: null as any,
},
data: () => ({
isActive: false,
originalValue: null as any,
}),
watch: {
isActive (val) {
if (val) {
this.originalValue = this.returnValue
} else {
this.$emit('update:return-value', this.originalValue)
}
},
},
methods: {
save (value: any) {
this.originalValue = value
setTimeout(() => {
this.isActive = false
})
},
},
})
Retrunable模块代码比较少,写的是一个Vue子类,对isActive进行监听,为真时,用originalValue
属性将returnValue
的值保存,当为假时,把originalValue
的值更新到returnValue
。
下面就是“保存”方法了,它就是简单的把传入的值保存到originalValue
,然后将isActive
设为假,然后就有了上一段的动作。
到这里,我们就知道为什么我在input上已经对值进行了v-model
双向绑定了,却还是有保存和取消的效果,都是这个模块在做这个事。
现在就可以知道,它这个模块名命名为Returnable
的意思了,意思是有可退回的能力。
这一点我觉得很值得我们学习,Vuetify把所有可以抽象的能力都抽象出来一个子类中,除了这个类,还有上面说的Themeable
,就是拥有主题继承及设置能力,还有Colorable
,拥有设置颜色的能力,等等等等。可以说,Vuetify的代码让我是受益匪浅。
网友评论