Vue3 Hooks实战:5个高频业务场景的封装技巧(含代码示例)

Vue3 Hooks实战:5个高频业务场景的封装技巧(含代码示例)

如果你已经熟悉Vue3的基础语法,但在实际项目中面对重复的业务逻辑时,仍然在复制粘贴代码,那么这篇文章就是为你准备的。今天我们不谈那些老生常谈的“什么是Hooks”或者“为什么要用Hooks”,而是直接切入实际开发中最常见的五个业务场景,看看如何用Composition API将它们封装成可复用的Hooks。

我在最近的一个后台管理系统中,发现团队里不同开发者对同一个功能——比如表单验证——竟然有五种不同的实现方式。这不仅增加了维护成本,还导致新成员接手时一头雾水。正是这种经历让我意识到,一套标准化的Hooks封装方案有多么重要。

1. 表单处理:从混乱到优雅的蜕变

表单处理几乎是每个前端项目都绕不开的痛点。传统的做法是在每个表单组件里重复编写验证逻辑、提交处理和重置逻辑,代码冗余且难以维护。让我们看看如何用Hooks来改变这一现状。

1.1 基础表单Hook设计

先从一个最简单的表单Hook开始。假设我们有一个用户注册表单,包含用户名、邮箱和密码三个字段:

// useForm.js
import { reactive, toRefs } from 'vue'

export function useForm(initialValues = {}) {
  const formState = reactive({
    values: { ...initialValues },
    errors: {},
    isSubmitting: false,
    isDirty: false
  })

  // 更新表单值
  const setFieldValue = (field, value) => {
    formState.values[field] = value
    formState.isDirty = true
  }

  // 批量更新
  const setValues = (newValues) => {
    Object.assign(formState.values, newValues)
    formState.isDirty = true
  }

  // 重置表单
  const resetForm = () => {
    Object.keys(formState.values).forEach(key => {
      formState.values[key] = initialValues[key] || ''
    })
    formState.errors = {}
    formState.isSubmitting = false
    formState.isDirty = false
  }

  // 提交表单
  const submitForm = async (submitFn) => {
    if (formState.isSubmitting) return
    
    formState.isSubmitting = true
    try {
      await submitFn(formState.values)
    } catch (error) {
      // 处理提交错误
      console.error('表单提交失败:', error)
    } finally {
      formState.isSubmitting = false
    }
  }

  return {
    ...toRefs(formState),
    setFieldValue,
    setValues,
    resetForm,
    submitForm
  }
}

在组件中使用这个Hook:

<template>
  <form @submit.prevent="handleSubmit">
    <input 
      v-model="values.username" 
      @input="setFieldValue('username', $event.target.value)"
      :class="{ 'error': errors.username }"
    />
    <span v-if="errors.username">{
  
  { errors.username }}</span>
    
    <button type="submit" :disabled="isSubmitting">
      {
  
  { isSubmitting ? '提交中...' : '提交' }}
    </button>
  </form>
</template>

<script setup>
import { useForm } from '@/hooks/useForm'

const { 
  values, 
  errors, 
  isSubmitting, 
  setFieldValue, 
  submitForm 
} = useForm({
  username: '',
  email: '',
  password: ''
})

const handleSubmit = async () => {
  await submitForm(async (formData) => {
    // 这里调用API
    const response = await api.register(formData)
    return response
  })
}
</script>

1.2 集成验证功能

基础表单Hook虽然能用,但缺少验证功能。让我们增强它,支持多种验证规则:

// useFormWithValidation.js
import { reactive, computed, toRefs } from 'vue'

// 验证规则定义
const validators = {
  required: (value) => !!value || '该字段不能为空',
  email: (value) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    return emailRegex.test(value) || '请输入有效的邮箱地址'
  },
  minLength: (min) => (value) => 
    value.length >= min || `至少需要${min}个字符`,
  maxLength: (max) => (value) => 
    value.length <= max || `不能超过${max}个字符`,
  pattern: (regex, message) => (value) =>
    regex.test(value) || message
}

export function useFormWithValidation(initialValues = {}, rules = {}) {
  const formState = reactive({
    values: { ...initialValues },
    errors: {},
    touched: {},
    isSubmitting: false,
    isDirty: false
  })

  // 计算属性:表单是否有效
  const isValid = computed(() => {
    return Object.keys(formState.errors).every(key => !formState.errors[key])
  })

  // 验证单个字段
  const validateField = (fieldName) => {
    const fieldRules = rules[fieldName]
    if (!fieldRules) return true

    const value = formState.values[fieldName]
    let error = ''

    for (const rule of fieldRules) {
      const result = typeof rule === 'function' 
        ? rule(value) 
        : rule(value)
      
      if (typeof result === 'string') {
        error = result
        break
      }
    }

    formState.errors[fieldName] = error
    return !error
  }

  // 验证整个表单
  const validateForm = () => {
    let isValid = true
    Object.keys(rules).forEach(fieldName => {
      const fieldValid = validateField(fieldName)
      if (!fieldValid) isValid = false
    })
    return isValid
  }

  // 带验证的字段更新
  const setFieldValue = (field, value) => {
    formState.values[field] = value
    formState.touched[field] = true
    formState.isDirty = true
    
    // 如果字段已被触摸过,立即验证
    if (formState.touched[field]) {
      validateField(field)
    }
  }

  // 提交前验证
  const submitForm = async (submitFn) => {
    // 标记所有字段为已触摸
    Object.keys(formState.values).forEach(key => {
      formState.touched[key] = true
    })

    if (!validateForm()) {
      throw new Error('表单验证失败')
    }

    return await submitFn(formState.values)
  }

  return {
    ...toRefs(formState),
    isValid,
    setFieldValue,
    validateField,
    validateForm,
    submitForm
  }
}

使用示例:

// 在组件中定义验证规则
const validationRules = {
  username: [
    validators.required,
    validators.minLength(3),
    validators.maxLength(20)
  ],
  email: [
    validators.required,
    validators.email
  ],
  password: [
    validators.required,
    validators.minLength(8),
    validators.pattern(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
      '密码必须包含大小写字母和数字'
    )
  ]
}

const form = useFormWithValidation({
  username: '',
  email: '',
  password: ''
}, validationRules)

提示:在实际项目中,你可能会遇到更复杂的验证场景,比如异步验证(检查用户名是否已存在)、跨字段验证(确认密码)等。这些都可以通过扩展验证规则系统来实现。

1.3 性能优化与防抖验证

对于需要实时验证的场景(如检查用户名是否可用),频繁的API调用会导致性能问题。我们可以为验证添加防抖功能:

// useDebouncedValidation.js
import { ref, watch } from 'vue'
import { debounce } from 'lodash-es'

export function useDebouncedValidation(fieldValue, validationFn, delay = 500) {
  const error = ref('')
  const isValidating = ref(false)

  // 防抖验证函数
  const debouncedValidate = debounce(async (value) => {
    isValidating.value = true
    try {
      const result = await validationFn(value)
      error.value = result || ''
    } catch (err) {
      error.value = '验证失败'
    } finally {
      isValidating.value = false
    }
  }, delay)

  // 监听字段值变化
  watch(fieldValue, (newValue) => {
    if (newValue) {
      debouncedValidate(newValue)
    } else {
      error.value = ''
    }
  }, { immediate: true })

  return {
    error,
    isValidating
  }
}

2. 表格分页与数据请求的完美结合

后台管理系统中最常见的组件就是数据表格,而表格往往伴随着分页、排序、筛选和加载状态管理。让我们创建一个强大的表格Hook来处理这些复杂逻辑。

2.1 基础表格Hook

// useTable.js
import { reactive, toRefs, computed } from 'vue'

export function useTable(fetchApi, options = {}) {
  const {
    pageSize = 10,
    immediate = true,
    transformResponse,
    transformRequest
  } = options

  const state = reactive({
    data: [],
    loading: false,
    error: null,
    pagination: {
      current: 1,
      pageSize,
      total: 0,
      totalPages: 0
    },
    filters: {},
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值