Vue3 封装一个基于element-plus的弹窗组件

文摘   2024-11-06 08:45   湖北  

v-model 的本质

Vue3 中的 v-model 本质上是一个语法糖,它展开后包含两个部分:

  • • 一个 modelValue 的 prop

  • • 一个 update:modelValue 的事件


<!-- 使用 v-model -->
<ChildComponent v-model="searchText" />

<!-- 等价于 -->
<ChildComponent 
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

自定义 v-model 名称

Vue3 允许我们自定义 v-model 的名称,使用方式如下:

<!-- 父组件 -->
<ChildComponent v-model:visible="isVisible" />

<!-- 等价于 -->
<ChildComponent 
  :visible="isVisible"
  @update:visible="newValue => isVisible = newValue"
/>

多个 v-model

Vue3 支持在同一个组件上使用多个 v-model:

<!-- 父组件 -->
<UserDialog
  v-model:visible="isVisible"
  v-model:username="username"
  v-model:age="age"
/>

Dialog 组件封装

完整的 v-model 实现示例

<template>
    <el-dialog
      v-model="dialogVisible"
      title="新增类别"
      width="400px"
      destroy-on-close
      center
      @close="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef">
        <el-form-item label="类别名称" :label-width="formLabelWidth" prop="categoryName">
          <el-input v-model="form.categoryName" placeholder="请输入类别名称" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDialog">取消</el-button>
          <el-button type="primary" @click="confirmAddCategory">确认</el-button>
        </div>
      </template>
    </el-dialog>
  </template>
  
  <script setup lang="ts">
  import { ref, computed, defineProps, defineEmits } from 'vue'
  import type { FormInstance } from 'element-plus'
  
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true
    },
    formLabelWidth: {
      type: String,
      default: '80px'
    }
  })
  
  const emits = defineEmits(['update:visible', 'addCategory'])
  
  // 使用计算属性来同步对话框的显示状态
  const dialogVisible = computed({
    get() {
      return props.visible
    },
    set(value) {
      emits('update:visible', value)
    }
  })
  
  const formRef = ref<FormInstance>()
  const form = ref({
    categoryName: ''
  })
  
  const rules = {
    categoryName: [
      { required: true, message: '请输入类别名称', trigger: 'blur' },
      { min: 2, max: 50, message: '类别名称长度在 2 到 50 个字符', trigger: 'blur' }
    ]
  }
  
  const closeDialog = () => {
    dialogVisible.value = false
    form.value.categoryName = ''
    formRef.value?.resetFields()
  }
  
  const confirmAddCategory = () => {
    if (!formRef.value) return
    
    formRef.value.validate((valid: boolean) => {
      if (valid) {
        emits('addCategory', form.value.categoryName.trim())
        closeDialog()
      }
    })
  }
  </script>
  
  <style scoped>
  .dialog-footer {
    padding-transform: translateY( 20px;
  }
  </style>

核心知识点详解

computed 实现双向绑定

// 1. 基础版本
const dialogVisible = computed({
  get() {
    return props.visible
  },
  set(value) {
    emits('update:visible', value)
  }
})

// 2. 带类型的完整版本
const dialogVisible = computed<boolean>({
  get() {
    return props.visible
  },
  set(valueboolean) {
    emits('update:visible', value)
  }
})

TypeScript 类型支持

// 1. Props 类型定义
interface Props {
  visibleboolean
  modelValue?: string
}

// 2. Emits 类型定义
interface Emits {
  (e'update:visible'valueboolean): void
  (e'update:modelValue'valuestring): void
  (e'close'): void
}

// 3. 使用类型
const props = withDefaults(defineProps<Props>(), {
  modelValue''
})

const emits = defineEmits<Emits>()

常见使用场景

<!-- 1. 基础用法 -->
<CategoryDialog v-model:visible="isVisible" />

<!-- 2. 带默认值的使用 -->
<CategoryDialog 
  v-model:visible="isVisible"
  :model-value="defaultCategory"
/>

<!-- 3. 多个 v-model 的使用 -->
<CategoryDialog 
  v-model:visible="isVisible"
  v-model:category-name="categoryName"
  v-model:category-type="categoryType"
/>

最佳实践和注意事项

Props 默认值设置

// 使用 withDefaults 设置默认值
const props = withDefaults(defineProps<Props>(), {
  visiblefalse,
  modelValue'',
  width'400px'
})

状态重置处理

const resetState = () => {
  // 1. 重置表单
  formRef.value?.resetFields()
  
  // 2. 重置本地状态
  form.value = {
    categoryName''
  }
  
  // 3. 通知父组件
  emits('update:modelValue''')
}

// 在关闭时调用
const handleClose = () => {
  resetState()
  emits('close')
}

组件封装技巧

// 1. 使用 defineExpose 暴露方法
const open = () => {
  dialogVisible.value = true
}

const close = () => {
  dialogVisible.value = false
}

defineExpose({
  open,
  close
})

// 2. 使用方式
const dialogRef = ref()
dialogRef.value?.open()

使用示例

<!-- 父组件 -->
<template>
  <div>
    <el-button @click="openDialog">打开对话框</el-button>
    
    <CategoryDialog
      v-model:visible="dialogVisible"
      @addCategory="handleAddCategory"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const dialogVisible = ref(false)

const openDialog = () => {
  dialogVisible.value = true
}

const handleAddCategory = (category: string) => {
  console.log('新增类别:', category)
  dialogVisible.value = false
}
</script>


字节笔记本
专注于科技领域的分享,AIGC,全栈开发,产品运营
 最新文章