Vue3组合式API封装业务逻辑的最佳实践

组合式函数(Composables)设计原则

Vue3的组合式API将逻辑关注点从选项式API的分散组织(data、methods、computed分置)转变为按功能聚合的Composables。一个设计良好的Composable应满足:单一职责、输入参数可响应式、返回值包含响应式状态和方法、支持生命周期钩子注入。

核心命名规范:Composable函数以use前缀开头,如useUserList、usePagination。返回值使用解构赋值,不要直接返回reactive对象(避免丢失响应性)。

实战:分页查询Composable封装

分页查询是后台管理系统中最常见的交互模式,封装为通用Composable后各列表页复用:

import { ref, computed, watch } from "vue"
import type { Ref } from "vue"

interface PaginationState<T> {
  data: Ref<T[]>
  total: Ref<number>
  loading: Ref<boolean>
  currentPage: Ref<number>
  pageSize: Ref<number>
  fetch: (params?: Record<string, unknown>) => Promise<void>
  changePage: (page: number) => void
  changePageSize: (size: number) => void
}

export function usePagination<T>(
  fetchFn: (params: {
    page: number
    pageSize: number
    [key: string]: unknown
  }) => Promise<{ data: T[]; total: number }>,
  options: { defaultPageSize?: number; immediate?: boolean } = {}
): PaginationState<T> {
  const data = ref<T[]>([]) as Ref<T[]>
  const total = ref(0)
  const loading = ref(false)
  const currentPage = ref(1)
  const pageSize = ref(options.defaultPageSize ?? 10)

  const fetch = async (
    extraParams: Record<string, unknown> = {}
  ) => {
    loading.value = true
    try {
      const result = await fetchFn({
        page: currentPage.value,
        pageSize: pageSize.value,
        ...extraParams,
      })
      data.value = result.data
      total.value = result.total
    } finally {
      loading.value = false
    }
  }

  const changePage = (page: number) => {
    currentPage.value = page
  }

  const changePageSize = (size: number) => {
    pageSize.value = size
    currentPage.value = 1
  }

  watch([currentPage, pageSize], () => {
    fetch()
  })

  if (options.immediate !== false) {
    fetch()
  }

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

Composable组合:表单与校验联动

实际业务中,Composable需要与其他逻辑组合使用。以下示例展示表单提交Composable与校验Composable的组合:

export function useFormSubmit<
  T extends Record<string, unknown>
>(
  submitFn: (form: T) => Promise<void>,
  options: { resetOnSuccess?: boolean } = {}
) {
  const submitting = ref(false)
  const errorMsg = ref("")

  const handleSubmit = async (form: T) => {
    submitting.value = true
    errorMsg.value = ""
    try {
      await submitFn(form)
    } catch (err: unknown) {
      errorMsg.value = err instanceof Error
        ? err.message
        : "提交失败"
      throw err
    } finally {
      submitting.value = false
    }
  }

  return { submitting, errorMsg, handleSubmit }
}

// 在组件中组合使用
const { data, fetch, loading } = usePagination(fetchUserList)
const { submitting, handleSubmit } = useFormSubmit(createUser, {
  resetOnSuccess: true,
})

const onCreate = async (form: UserForm) => {
  await handleSubmit(form)
  fetch()
}

响应式布局中的Composable应用

在响应式布局场景下,Composable可封装断点检测逻辑,避免各组件重复监听resize事件:

import { ref, computed, onMounted, onUnmounted } from "vue"

interface Breakpoints {
  isMobile: Ref<boolean>
  isTablet: Ref<boolean>
  isDesktop: Ref<boolean>
  width: Ref<number>
}

export function useBreakpoints(): Breakpoints {
  const width = ref(window.innerWidth)
  const isMobile = computed(() => width.value < 768)
  const isTablet = computed(() =>
    width.value >= 768 && width.value < 1024
  )
  const isDesktop = computed(() => width.value >= 1024)

  const onResize = () => {
    width.value = window.innerWidth
  }

  onMounted(() =>
    window.addEventListener("resize", onResize)
  )
  onUnmounted(() =>
    window.removeEventListener("resize", onResize)
  )

  return { isMobile, isTablet, isDesktop, width }
}

前端工程化:Composable的单元测试

Composable作为纯逻辑单元,天然适合单元测试。使用@vue/test-utils的mount + setup方式:

import { usePagination } from "../composables/usePagination"
import { describe, it, expect, vi } from "vitest"

describe("usePagination", () => {
  it("应在首次挂载时自动请求数据", async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      data: [{ id: 1 }],
      total: 1,
    })

    const { data, total, loading } = usePagination(
      mockFetch, { immediate: false }
    )

    await mockFetch({ page: 1, pageSize: 10 })
    expect(mockFetch).toHaveBeenCalled()
  })

  it("切换页码应触发重新查询", async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      data: [],
      total: 0,
    })
    const { changePage, currentPage } = usePagination(
      mockFetch, { immediate: false }
    )

    changePage(3)
    expect(currentPage.value).toBe(3)
  })
})

Composable封装的核心价值在于:将业务逻辑从组件中剥离,使组件模板只负责UI渲染,逻辑层可独立测试、复用和组合。这种分层架构显著提升了大型Vue3项目的可维护性。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-feng-zhuang-ye-wu-luo-ji-de-zui-jia-shi/

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

相关推荐