Vue3组合式函数设计模式:从可复用逻辑到类型安全的进阶实践

组合式函数的设计原则

Vue3的组合式API(Composition API)让逻辑复用从mixin的隐式依赖进化为显式的函数组合。composable函数是Vue3生态中最核心的代码组织单元——它封装响应式状态和副作用,通过参数和返回值明确接口契约。这篇文章从设计原则、常见模式到TypeScript类型安全,给出组合式函数的完整工程化实践。

基础模式:状态封装与响应式返回

最简单的composable封装一段可复用的响应式逻辑。以鼠标位置追踪为例:

import { ref, onMounted, onUnmounted } from 'vue'

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)

  function update(event: MouseEvent) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

使用方直接解构调用:const { x, y } = useMousePosition()。ref保持响应性,模板中自动解包。关键设计点:副作用(事件监听)在onMounted中注册,onUnmounted中清理,避免内存泄漏。

进阶模式:参数化与配置驱动

实际业务中的composable需要接受参数控制行为。以分页数据请求为例:

import { ref, watch, toValue } from 'vue'
import type { Ref } from 'vue'

interface UsePaginationOptions<T> {
  fetcher: (page: number, size: number) => Promise<{ data: T[]; total: number }>
  pageSize?: number
  immediate?: boolean
}

export function usePagination<T>(options: UsePaginationOptions<T>) {
  const currentPage = ref(1)
  const pageSize = ref(options.pageSize ?? 20)
  const data = ref<T[]>([]) as Ref<T[]>
  const total = ref(0)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  async function execute() {
    loading.value = true
    error.value = null
    try {
      const result = await options.fetcher(currentPage.value, pageSize.value)
      data.value = result.data
      total.value = result.total
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  watch([currentPage, pageSize], execute)

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

  return { data, total, loading, error, currentPage, pageSize, refresh: execute }
}

参数通过接口类型UsePaginationOptions约束,泛型T让返回的data保留元素类型信息。watch自动响应分页参数变化,immediate控制是否立即执行。这种模式覆盖了大部分异步数据场景。

组合模式:composable之间的依赖编排

composable可以组合其他composable,构建更复杂的业务逻辑。以带防抖搜索的分页列表为例:

import { ref, watch } from 'vue'
import { useDebounce } from './useDebounce'
import { usePagination } from './usePagination'

interface SearchResult {
  id: string
  title: string
}

export function useSearchList(apiUrl: string) {
  const keyword = ref('')
  const debouncedKeyword = useDebounce(keyword, 300)

  const pagination = usePagination<SearchResult>({
    fetcher: (page, size) =>
      fetch(`${apiUrl}?q=${debouncedKeyword.value}&page=${page}&size=${size}`)
        .then(r => r.json()),
    pageSize: 10,
  })

  watch(debouncedKeyword, () => {
    pagination.currentPage.value = 1
    pagination.refresh()
  })

  return { keyword, ...pagination }
}

useDebounce提供防抖keyword,usePagination管理分页状态。组合后的useSearchList对外暴露简洁接口,内部各composable各司其职。这种方式比把所有逻辑写在一个setup函数中清晰得多。

TypeScript类型安全:泛型约束与类型推导

composable的TypeScript类型设计要兼顾灵活性和安全性。几个关键实践:

1. Ref解包类型computed返回ComputedRef<T>ref返回Ref<T>,在模板中自动解包为T。导出时不要用as const断言,会破坏响应性。

2. 可选参数用MaybeRef<T>:让参数既接受原始值也接受ref:

import type { Ref } from 'vue'
type MaybeRef<T> = T | Ref<T>

export function useFetch<T>(url: MaybeRef<string>) {
  // toValue() 同时处理 Ref 和原始值
  const resolvedUrl = toValue(url)
  // ...
}

3. 返回类型显式标注:复杂的composable返回值建议用interface定义,避免推导丢失ref信息:

interface UseFetchReturn<T> {
  data: Readonly<Ref<T | null>>
  error: Ref<Error | null>
  loading: Ref<boolean>
  execute: () => Promise<void>
}

这些类型约束确保composable的接口在编译期就可被发现错误,减少运行时排查成本。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-she-ji-mo-shi-cong-ke-fu-yong-luo-ji/

(0)
小编小编
上一篇 16小时前
下一篇 15小时前

相关推荐