Vue3组合式API企业级组件设计模式:从逻辑复用到类型安全的完整方案

组合式API解决的核心问题

Vue2的OptionsAPI在大型项目中最突出的问题是逻辑分散。一个表单组件的验证逻辑、提交逻辑、状态管理散布在data、methods、computed、watch中,改一个功能要在四个地方跳转。组合式API把相关逻辑聚合到一起,一个useXxx函数就是一个完整的关注点。

这不是风格偏好的问题,而是工程可维护性的选择。组件超过200行后,OptionsAPI的维护成本呈指数级增长,组合式API保持线性。

Composable函数设计规范

企业级Composable必须满足五个条件:TypeScript类型完备、参数可响应式、副作用可清理、支持SSR、有完整的单元测试。

以表单管理Composable为例:

// composables/useForm.ts
import { ref, reactive, computed, type Ref } from 'vue'

interface FieldRule<T> {
  required?: boolean
  min?: number
  max?: number
  pattern?: RegExp
  validator?: (value: T) => string | null
}

interface FieldConfig<T> {
  defaultValue: T
  rules?: FieldRule<T>[]
}

type FormSchema<T extends Record<string, any>> = {
  [K in keyof T]: FieldConfig<T[K]>
}

export function useForm<T extends Record<string, any>>(schema: FormSchema<T>) {
  const values = reactive({} as T) as T
  const errors = reactive({} as Record<keyof T, string | null>)
  const touched = reactive({} as Record<keyof T, boolean>)

  for (const key in schema) {
    values[key] = schema[key].defaultValue
    errors[key] = null
    touched[key] = false
  }

  function validateField(key: keyof T): boolean {
    const rules = schema[key].rules || []
    const value = values[key]

    for (const rule of rules) {
      if (rule.required && (value === null || value === undefined || value === '')) {
        errors[key] = '此字段为必填项'
        return false
      }
      if (rule.min !== undefined && typeof value === 'string' && value.length < rule.min) {
        errors[key] = '最少输入' + rule.min + '个字符'
        return false
      }
      if (rule.validator) {
        const msg = rule.validator(value)
        if (msg) {
          errors[key] = msg
          return false
        }
      }
    }

    errors[key] = null
    return true
  }

  const isValid = computed(() => {
    return Object.keys(errors).every(key => errors[key as keyof T] === null)
  })

  const isDirty = computed(() => {
    return Object.keys(schema).some(key => {
      return values[key] !== schema[key].defaultValue
    })
  })

  return { values, errors, touched, isValid, isDirty, validateField }
}

组件通信模式进阶

Props + Emits适合父子通信,跨层级通信推荐Provide/Inject + Symbol Key:

// composables/useTableContext.ts
import { inject, provide, type InjectionKey } from 'vue'

interface TableContext {
  selectedKeys: Ref<Set<string>>
  toggleRow: (key: string) => void
  pageSize: Ref<number>
  currentPage: Ref<number>
}

const TABLE_KEY: InjectionKey<TableContext> = Symbol('table-context')

export function provideTableContext(ctx: TableContext) {
  provide(TABLE_KEY, ctx)
}

export function useTableContext(): TableContext {
  const ctx = inject(TABLE_KEY)
  if (!ctx) {
    throw new Error('useTableContext must be used within Table component')
  }
  return ctx
}

Symbol Key保证类型安全且不会冲突,inject失败时抛出明确错误而非返回undefined让下游崩溃。

逻辑复用的分层架构

企业级前端应用的逻辑复用分三层:

基础设施层:useRequest、useStorage、useEventBus——与UI无关的通用逻辑
业务逻辑层:useAuth、usePermission、useCart——与业务领域绑定的逻辑
UI交互层:useForm、useTable、useModal——与UI组件绑定的交互逻辑

// 基础设施层:通用请求
export function useRequest<T>(fetcher: () => Promise<T>) {
  const data = ref<T | null>(null) as Ref<T | null>
  const error = ref<Error | null>(null)
  const loading = ref(false)

  async function execute() {
    loading.value = true
    error.value = null
    try {
      data.value = await fetcher()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  return { data, error, loading, execute }
}

// 业务逻辑层:用户权限
export function useAuth() {
  const { data: user, execute: fetchUser } = useRequest(() => 
    api.getCurrentUser()
  )
  
  const isAdmin = computed(() => user.value?.role === 'admin')
  
  function hasPermission(perm: string): boolean {
    return user.value?.permissions?.includes(perm) ?? false
  }

  return { user, isAdmin, hasPermission, fetchUser }
}

// UI交互层:列表分页
export function usePagination(fetchFn: (page: number, size: number) => Promise<any>) {
  const currentPage = ref(1)
  const pageSize = ref(20)
  const total = ref(0)

  const { data, loading, execute } = useRequest(() =>
    fetchFn(currentPage.value, pageSize.value)
  )

  function changePage(page: number) {
    currentPage.value = page
    execute()
  }

  return { data, loading, currentPage, pageSize, total, changePage }
}

三层分离让每一层都可以独立测试、独立复用。usePagination不关心数据从哪来,useAuth不关心数据怎么展示,useRequest不关心业务是什么。

组件设计的边界原则

什么逻辑该放组件内,什么该抽Composable?判断标准:

出现两次就抽——相同逻辑在两个组件中出现,立即抽取,不要等三次。
超过50行就抽——script setup超过50行,必有可抽取的逻辑。
有副作用就抽——涉及API调用、DOM操作、定时器、事件监听,必须抽到Composable中统一管理生命周期。
纯展示逻辑留组件内——只涉及当前组件UI状态的逻辑(展开折叠、hover状态)没必要抽。

Vue3组合式API的设计价值不在于”新写法”,而在于把散落在OptionsAPI各处的逻辑重新聚合成内聚的函数单元,每个Composable只做一件事,做好一件事。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-qi-ye-ji-zu-jian-she-ji-mo-shi-cong-luo/

(0)
小编小编
上一篇 18小时前
下一篇 18小时前

相关推荐