Vue3组合式函数Composables封装模式与性能优化技巧

Composables设计原则与封装模式

Vue3组合式API的核心优势在于逻辑复用。Composables(组合式函数)取代了Vue2的Mixins,解决了命名冲突和数据来源不透明的痛点。一个规范的Composable遵循use前缀命名,接受ref或getter作为参数,返回响应式状态和方法。前端开发中善用Composables可以把复杂业务逻辑从组件中解耦,组件文件从动辄300行压缩到50行以内。

基础Composable封装模式

// useRequest - 通用异步请求封装
import { ref, readonly } from 'vue'

export function useRequest(fetcher, options = {}) {
  const {
    immediate = true,
    initialData = null,
    onSuccess,
    onError
  } = options

  const data = ref(initialData)
  const loading = ref(false)
  const error = ref(null)

  async function execute(...args) {
    loading.value = true
    error.value = null
    try {
      data.value = await fetcher(...args)
      onSuccess?.(data.value)
    } catch (e) {
      error.value = e
      onError?.(e)
    } finally {
      loading.value = false
    }
  }

  if (immediate) execute()

  return {
    data: readonly(data),
    loading: readonly(loading),
    error: readonly(error),
    execute,
    refresh: execute
  }
}

// 组件中使用
const { data: users, loading, execute: fetchUsers } = useRequest(
  () => api.get('/api/users'),
  { initialData: [] }
)

高级模式:带防抖的搜索Composable

// useDebounceSearch - 防抖搜索 + 请求取消
import { ref, watch, onUnmounted } from 'vue'

export function useDebounceSearch(searchFn, delay = 300) {
  const query = ref('')
  const results = ref([])
  const searching = ref(false)
  let timer = null
  let abortController = null

  async function doSearch(keyword) {
    // 取消上一次未完成的请求
    abortController?.abort()
    abortController = new AbortController()

    if (!keyword.trim()) {
      results.value = []
      return
    }

    searching.value = true
    try {
      results.value = await searchFn(keyword, {
        signal: abortController.signal
      })
    } catch (e) {
      if (e.name !== 'AbortError') {
        console.error('Search failed:', e)
      }
    } finally {
      searching.value = false
    }
  }

  watch(query, (val) => {
    clearTimeout(timer)
    timer = setTimeout(() => doSearch(val), delay)
  })

  onUnmounted(() => {
    clearTimeout(timer)
    abortController?.abort()
  })

  return { query, results, searching }
}

// 使用示例
const { query, results, searching } = useDebounceSearch(
  async (keyword, { signal }) => {
    const resp = await fetch(`/api/search?q=${keyword}`, { signal })
    return resp.json()
  },
  500
)

响应式性能陷阱与优化

Vue3的响应式系统基于Proxy,但不当使用会产生不必要的依赖追踪和重复渲染:

// ❌ 常见性能陷阱

// 1. 在模板中使用复杂计算属性
// 每次响应式数据变化都会重新执行
const heavyList = computed(() => {
  return data.value.map(item => ({
    ...item,
    processed: expensiveTransform(item)  // 昂贵操作放computed
  }))
})

// ✅ 优化:用shallowRef + 手动触发
const heavyList = shallowRef([])
watch(data, async (newData) => {
  // 耗时计算放到Web Worker
  heavyList.value = await workerProcess(newData)
}, { immediate: true })

// 2. ❌ 大数组整体响应式
const bigArray = reactive(createLargeArray(10000))
// 任何元素变化都触发整个数组依赖更新

// ✅ 优化:使用markRaw冻结不需要响应式的对象
import { markRaw } from 'vue'
const items = ref(data.map(item => ({
  id: item.id,
  data: markRaw(item)  // 大对象不参与响应式追踪
})))

// 3. ❌ v-for没有key或使用index作key
<div v-for="(item, index) in list" :key="index">

// ✅ 使用唯一ID作key
<div v-for="item in list" :key="item.id">

Composable单元测试方法

// useRequest.test.js
import { useRequest } from './useRequest'

// 使用@vue/test-utils的withSetup辅助函数
function withSetup(composable) {
  let result
  const app = createApp({
    setup() {
      result = composable()
      return () => {}
    }
  })
  const root = document.createElement('div')
  app.mount(root)
  return { result, app }
}

test('useRequest - 成功请求', async () => {
  const mockData = { id: 1, name: 'test' }
  const fetcher = jest.fn().mockResolvedValue(mockData)

  const { result } = withSetup(() => useRequest(fetcher))

  expect(result.loading.value).toBe(true)

  await flushPromises()

  expect(result.data.value).toEqual(mockData)
  expect(result.loading.value).toBe(false)
  expect(result.error.value).toBe(null)
  expect(fetcher).toHaveBeenCalledTimes(1)
})

test('useRequest - 请求失败', async () => {
  const fetcher = jest.fn().mockRejectedValue(new Error('Network Error'))

  const { result } = withSetup(() => useRequest(fetcher, { immediate: false }))

  await result.execute()
  await flushPromises()

  expect(result.error.value.message).toBe('Network Error')
  expect(result.data.value).toBe(null)
})

Composables是Vue3生态的逻辑复用基石。封装时保持单一职责,每个Composable只做一件事;返回值用readonly包裹防止外部意外修改;副作用(定时器、事件监听)必须在onUnmounted中清理。遵循这些规则,Composables才能真正实现即插即用。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-composables-feng-zhuang-mo-shi-yu/

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

相关推荐