vue封装dialog弹框,体验computed中的get和set

147 阅读2分钟

一、vue2

效果:

动画.gif

@/components/TestDialog.vue

<template>
  <el-dialog
    title="弹框"
    :visible.sync="dialogVisible"
    :before-close="handleClose"
  >
    <span>这是一段信息</span>
    <div slot="footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleSubmit">确 定</el-button>
    </div>
  </el-dialog>
</template>
<script>
export default {
  name: 'TestDialog',
  props: { value: { type: Boolean, default: false } },
  computed: {
    dialogVisible: {
      get() {
        return this.value
      },
      set(flag) {
        this.$emit('input', flag)
      }
    }
  },
  methods: {
    handleSubmit() {
      this.handleClose()
    },
    handleClose() {
      this.dialogVisible = false
    }
  }
}
</script>

如果不使用computed:

<template>
  <el-dialog title="标题" :visible.sync="value" :before-close="handleClose">
    <span>这是一段信息</span>
    <div slot="footer">
      <el-button @click="handleClose">取 消</el-button>
      <el-button type="primary" @click="handleSubmit">确 定</el-button>
    </div>
  </el-dialog>
</template>
<script>
  export default {
    name: 'TestDialog',
    props: { value: { type: Boolean, default: false } },
    watch: {
      value(flag) {},
    },
    methods: {
      handleSubmit() {
        this.handleClose()
      },
      handleClose() {
        this.$emit('input', false)
      },
    },
  }
</script>

如果需要设置弹框样式:

<style lang="scss" scoped>
  ::v-deep .el-dialog__body {
    padding: 0 40px;
  }
</style>

App.vue

<template>
  <div id="app">
    <el-button @click="open">打开</el-button>
    <TestDialog v-model="dialogVisible" />
  </div>
</template>
<script>
export default {
  data() {
    return { dialogVisible: false }
  },
  methods: {
    open() {
      this.dialogVisible = true
    }
  }
}
</script>

拓展:sync修饰符

细心的小伙伴发现el-dialog标签使用的是:visible.sync="dialogVisible",如果我想要TestDialog标签也使用:visible.sync="dialogVisible",该怎么实现呢

    <TestDialog :visible.sync="dialogVisible" />

TestDialog.vue

image.png

为什么我建议封装弹框组件使用v-model或sync?

这样封装完组件,在使用组件的时候只需要绑定v-model或visible.sync属性就可以实现开启和关闭弹框,不需要借助ref或其他方式去实现,在繁复的代码中,能简洁一点是一点

同样地,在封装一些表单组件时,也应该尽量考虑使用v-model去绑定数据,ref只是一种方式,但不是首选

二、vue3 基于computed封装一个dialog弹框

动画.gif

data-situation-dialog.vue

<template>
  <el-dialog v-model="dialogVisible" title="Tips" width="500" :before-close="handleClose">
    <span>This is a message</span>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleClose">Cancel</el-button>
        <el-button type="primary" @click="handleConfirm">Confirm</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
const props = defineProps({ modelValue: { type: Boolean, default: false } })
const emit = defineEmits(["update:modelValue"])
const dialogVisible = computed({
  get() {
    return props.modelValue
  },
  set(val) {
    emit("update:modelValue", val)
  },
})

const handleClose = () => {
  dialogVisible.value = false
}
const handleConfirm = () => {
  handleClose()
}
</script>
<style lang="scss" scoped></style>

使用:

  <DataSituationDialog v-model="dataSituationVisible" />

import DataSituationDialog from "./components/data-situation-dialog.vue"

const dataSituationVisible = ref<boolean>(false)

vue3 - computed的get方法中进行异步请求

const dialogVisible = computed({
  get() {
    if (props.modelValue) getData() // 打开弹框时,触发搜索
    return props.modelValue
  },
  set(val) {
    emit("update:modelValue", val)
  },
})

问题复现:(v2中不存在此问题)

动画.gif

问题原因:

【如果你在computed属性的get方法中使用了异步操作,那么每次调用get方法都会执行该异步操作。这是因为Vue无法预测异步操作何时完成,因此它无法确定是否需要重新计算computed属性的值。

为了解决这个问题,你可以使用watcher或methods来处理异步逻辑。watcher可以监听特定的数据变化,并在变化时触发异步操作。而methods则可以在需要的时候手动调用异步操作。】

解决方式:通过watch监听去触发搜索

image.png

computed就不需要了,直接v-model="props.modelValue"

在弹框关闭时执行emit('update:modelValue', false)