ElementUI:干掉dialog,用messagebox来实现吧

11,531

写一个几个文字加个el-select框的玩意,有必要用el-dialog这么麻烦的东西么。写个el-dialog还要加一堆乱七八糟的属性如:width、top、append-to-body、close-on-click-modal,还有el-dialog默认是不带footer按钮的,还需要在footer里加上el-button确定才行。另外内部的样式还要重新写一遍,el-messagebox里面的样式不香么。这很明显不是我想要的开发模式。

毕竟比尔盖茨说过:能用service做的 dialog,就尽量不要用template做的 dialog

比尔.png

言归正传。于是,我开始了用el-messagebox代替el-dialog的开发征程。看了一下element官方文档,对话框内容message可以用$createElement去实现

20211129170142.jpg

vue2的写法

那么我就按照官网的写法来做:

<template>
  <el-button @click="open">打开弹窗</el-button>
</template>
<script>
export default {
  methods: {
    async open() {
      let sex = '未知'
      const h = this.$createElement
      await this.$msgbox({
        title: '提示',
        message: h('p', [
          '选择性别',
          h('el-select', {
            props: {
              value: sex
            },
            on: {
              change: val => (sex = val)
            },
            style: {
              marginLeft: '1em'
            }
          }, ['男', '女', '未知'].map(sex => {
            return h('el-option', {
              key: sex,
              props: {
                label: sex,
                value: sex
              }
            })
          }))
        ])
      })
      alert(sex)
    }
  }
}
</script>

效果图:

20211129171032.jpg

可是问题出现了,改变不了select的值???

屏幕录制2021-11-29 下午5.15.01.gif

百思不得其姐,那只好去看看element源码呗,发现原来是将message赋值给$slots.default

image.png

那么我立刻就想到message是vnode的话,何不去用VueComponent去试试呢?那么就可以改造成:

<template>
  <el-button @click="open">打开弹窗</el-button>
</template>
<script>
import Vue from 'vue'

const MessageboxSelect =  Vue.component('messagebox-select', {
  data() {
    return {
      value: '未知'
    }
  },
  render() {
    const h = this.$createElement
    return h('p', [
      '选择性别',
      h('el-select', {
        props: {
          value: this.value
        },
        on: {
          change: val => (this.value = val)
        },
        style: {
          marginLeft: '1em'
        }
      }, ['男', '女', '未知'].map(sex => {
        return h('el-option', {
          key: sex,
          props: {
            label: sex,
            value: sex
          }
        })
      }))
    ])
  }
})

export default {
  methods: {
    async open() {
      const h = this.$createElement
      await this.$msgbox({
        title: '提示',
        message: h(MessageboxSelect)
      })
    }
  }
}
</script>

但是这样会引发一个问题,弹窗消失后再打开会出现之前保留的状态,而不是重新生成初始状态

屏幕录制2021-11-29 下午9.41.01.gif

那么就好办了,只要把Vue.component()这一步放到open里面就可以了

<template>
  <el-button @click="open">打开弹窗</el-button>
</template>
<script>
import Vue from 'vue'

export default {
  methods: {
    async open() {
      let sex = '未知'
      const h = this.$createElement
      await this.$msgbox({
        title: '提示',
        message: h(Vue.component('messagebox-select', {
          data() {
            return {
              value: sex
            }
          },
          render() {
            const h = this.$createElement
            return h('p', [
              '选择性别',
              h('el-select', {
                props: {
                  value: this.value
                },
                on: {
                  change: val => (sex = this.value = val)
                },
                style: {
                  marginLeft: '1em'
                }
              }, ['男', '女', '未知'].map(sex => {
                return h('el-option', {
                  key: sex,
                  props: {
                    label: sex,
                    value: sex
                  }
                })
              }))
            ])
          }
        }))
      })
      alert(sex)
    }
  }
}
</script>

屏幕录制2021-11-29 下午9.49.33.gif

搞掂!!

附上vue3的写法

createVNode的形式(vue2叫createElement,3改成叫createVNode)

<template>
  <el-button @click="open">打开弹窗</el-button>
</template>
<script>
import {ref,createVNode} from 'vue'
import {ElMessageBox, ElSelect, ElOption} from 'element-plus'

export default {
  setup() {
    async function open() {
      let sex = '未知'
      const h = createVNode

      const elSelect = h(ElSelect,
      {
        modelValue: sex,
        'onUpdate:modelValue': val => (sex = elSelect.component.props.modelValue = val),
        style: {
          marginLeft: '1em'
        }
      }, 
      {
        default: () => ['男', '女', '未知'].map(sex => {
          return h(ElOption, {
            key: sex,
            label: sex,
            value: sex
          })
        })
      })

      await ElMessageBox({
        title: '提示',
        message: h(createVNode('p', {}, [
          '选择性别',
          elSelect
        ]))
      })
      alert(sex)
    }

    return {open}
  }
}
</script>

最关键的地方是:'onUpdate:modelValue': val => (sex = elSelect.component.props.modelValue = val),否则el-selectv-model效果就不生效。我找了所有的文档和stackoverflow、github tissue这些都没找到解决办法,最终自己去vnode的属性里面找到了嘿嘿(花了我好多时间)。

jsx的写法

<template>
  <el-button @click="open">打开弹窗</el-button>
</template>
<script>
import {ref,createVNode} from 'vue'
import {ElMessageBox, ElSelect, ElOption} from 'element-plus'

export default {
  setup(props, ctx) {
    async function open() {
      let sex = '未知'
      const h = createVNode

      const elSelect = h(<ElSelect
        modelValue={sex}
        onUpdate:modelValue={val => (sex = elSelect.component.props.modelValue = val)}
        style={{marginLeft: '1em'}}
      >
        {
          ['男', '女', '未知'].map(sex => {
            return h(<ElOption key={sex} label={sex} value={sex}></ElOption>)
          })
        }
      </ElSelect>)

      await ElMessageBox({
        title: '提示',
        message: h(<p>
          选择性别{elSelect}
        </p>)
      })
      alert(sex)
    }

    return {open}
  }
}
</script>