Vue 3高级特性:Composition API与自定义Hooks深度解析

Vue 3高级特性:Composition API与自定义Hooks深度解析

一、Composition API设计哲学

1. 与Options API对比

维度 Options API Composition API
代码组织 按选项类型分组(data/methods等) 按逻辑功能组织
复用性 Mixins/继承 自定义Hook函数
TypeScript支持 有限 完整支持
逻辑抽象 困难 灵活
学习曲线 平缓 较陡峭

2. 核心优势分析

  • 逻辑关注点分离:将同一功能的代码集中在一起
  • 更好的类型推断:完整的TypeScript支持
  • 更灵活的代码组织:不受模板选项限制
  • 更小的生产包体积:更好的Tree-shaking支持

二、响应式系统深度剖析

1. 响应式API对比

import { ref, reactive, shallowRef, shallowReactive } from 'vue'

// 完全响应式
const state = reactive({ 
  count: 0,
  user: { name: 'Alice' } // 嵌套对象也是响应式的
})

const num = ref(0) // 需要.value访问

// 浅层响应式
const shallowState = shallowReactive({
  nested: { count: 0 } // nested不会自动解包
})

const shallowNum = shallowRef(0) // 不追踪.value内部变化

2. 响应式转换原理

源码级响应式流程

  1. reactive()createReactiveObject()
  2. 使用Proxy拦截:
    • gettrack 依赖收集
    • settrigger 触发更新
  3. 依赖关系存储在全局targetMap

手动依赖控制

import { effect, stop } from '@vue/reactivity'

const runner = effect(() => {
  console.log(state.count)
})

// 手动停止响应
stop(runner)

三、自定义Hook开发模式

1. 企业级Hook设计原则

  • 单一职责:每个Hook只解决一个问题
  • 明确输入输出:参数和返回值类型明确定义
  • 副作用管理:清晰处理副作用生命周期
  • TypeScript优先:完整的类型定义

2. 高级Hook示例:useFetch

// hooks/useFetch.ts
import { ref, watchEffect, type Ref } from 'vue'

interface UseFetchOptions<T> {
  immediate?: boolean
  initialData?: T
  onSuccess?: (data: T) => void
  onError?: (error: Error) => void
}

export function useFetch<T>(
  url: Ref<string> | string,
  options: UseFetchOptions<T> = {}
) {
  const data = ref<T | undefined>(options.initialData)
  const error = ref<Error | null>(null)
  const isFetching = ref(false)
  const isFinished = ref(false)

  async function execute() {
    isFetching.value = true
    error.value = null
    
    try {
      const response = await fetch(unref(url))
      if (!response.ok) throw new Error(response.statusText)
      data.value = await response.json()
      options.onSuccess?.(data.value)
    } catch (err) {
      error.value = err as Error
      options.onError?.(error.value)
    } finally {
      isFetching.value = false
      isFinished.value = true
    }
  }

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

  return {
    data,
    error,
    isFetching,
    isFinished,
    execute,
    refresh: execute
  }
}

3. Hook组合示例

// hooks/usePagination.ts
import { ref, computed } from 'vue'
import { useFetch } from './useFetch'

interface UsePaginationOptions<T> {
  pageSize?: number
  initialPage?: number
  fetchOptions?: Omit<UseFetchOptions<T[]>, 'initialData'>
}

export function usePagination<T>(
  url: string,
  options: UsePaginationOptions<T> = {}
) {
  const currentPage = ref(options.initialPage || 1)
  const pageSize = ref(options.pageSize || 10)

  const { 
    data: items,
    error,
    isFetching,
    execute: refresh
  } = useFetch<T[]>(
    computed(() => `${url}?page=${currentPage.value}&size=${pageSize.value}`),
    {
      ...options.fetchOptions,
      immediate: true
    }
  )

  const total = ref(0)
  const totalPages = computed(() => Math.ceil(total.value / pageSize.value))

  async function loadPage(page: number) {
    if (page < 1 || page > totalPages.value) return
    currentPage.value = page
    await refresh()
  }

  return {
    items,
    currentPage,
    pageSize,
    total,
    totalPages,
    isFetching,
    error,
    loadPage,
    refresh
  }
}

四、依赖注入高级模式

1. 类型安全的provide/inject

// contexts/themeContext.ts
import { inject, provide, type InjectionKey, type Ref } from 'vue'

interface Theme {
  primaryColor: string
  secondaryColor: string
  darkMode: boolean
}

const ThemeSymbol: InjectionKey<Ref<Theme>> = Symbol.for('theme')

export function provideTheme(theme: Ref<Theme>) {
  provide(ThemeSymbol, theme)
}

export function useTheme() {
  const theme = inject(ThemeSymbol)
  if (!theme) throw new Error('Theme not provided!')
  return theme
}

2. 响应式状态共享

// composables/useSharedState.ts
import { reactive, readonly } from 'vue'

interface SharedState {
  user: {
    id: string
    name: string
  } | null
  permissions: string[]
}

const state = reactive<SharedState>({
  user: null,
  permissions: []
})

export function setUser(user: SharedState['user']) {
  state.user = user
}

export function setPermissions(permissions: string[]) {
  state.permissions = permissions
}

export function useSharedState() {
  return readonly(state) // 返回只读副本
}

五、Render函数与JSX进阶

1. 动态组件工厂模式

// components/ComponentFactory.tsx
import { defineComponent, type VNode } from 'vue'

interface ComponentDefinition {
  type: string
  props?: Record<string, any>
  children?: ComponentDefinition[]
}

export const ComponentFactory = defineComponent({
  props: {
    schema: {
      type: Object as () => ComponentDefinition,
      required: true
    }
  },
  setup(props) {
    const resolveComponent = (def: ComponentDefinition): VNode => {
      const { type, props: componentProps = {}, children = [] } = def
      
      return h(
        type,
        componentProps,
        children.map(child => resolveComponent(child))
      )
    }

    return () => resolveComponent(props.schema)
  }
})

2. 高阶组件实现

// components/withLoading.tsx
import { defineComponent, h, ref } from 'vue'

export function withLoading(component: any) {
  return defineComponent({
    setup(props, { slots }) {
      const isLoading = ref(false)
      
      const setLoading = (value: boolean) => {
        isLoading.value = value
      }

      return () => [
        h(component, {
          ...props,
          setLoading
        }, slots),
        isLoading.value && h('div', { class: 'loading-overlay' }, 'Loading...')
      ]
    }
  })
}

六、性能优化Hooks

1. useDebounce实现

import { ref, watch, type WatchSource } from 'vue'

export function useDebounce<T>(
  source: WatchSource<T>,
  delay: number = 200
) {
  const debouncedValue = ref<T>()

  let timeout: number
  watch(source, (newValue) => {
    clearTimeout(timeout)
    timeout = window.setTimeout(() => {
      debouncedValue.value = newValue
    }, delay)
  }, { immediate: true })

  return debouncedValue
}

2. useThrottle实现

import { ref, watch, type WatchSource } from 'vue'

export function useThrottle<T>(
  source: WatchSource<T>,
  interval: number = 200
) {
  const throttledValue = ref<T>()
  let lastExecuted = 0

  watch(source, (newValue) => {
    const now = Date.now()
    if (now - lastExecuted >= interval) {
      throttledValue.value = newValue
      lastExecuted = now
    }
  }, { immediate: true })

  return throttledValue
}

七、测试驱动开发实践

1. Hook测试策略

// tests/hooks/useCounter.spec.ts
import { renderHook } from '@testing-library/vue'
import { useCounter } from '@/hooks/useCounter'

describe('useCounter', () => {
  it('should increment count', () => {
    const { result } = renderHook(() => useCounter(0))
    
    expect(result.current.count.value).toBe(0)
    result.current.increment()
    expect(result.current.count.value).toBe(1)
  })

  it('should reset count', async () => {
    const { result } = renderHook(() => useCounter(10))
    
    result.current.reset()
    expect(result.current.count.value).toBe(0)
  })
})

2. 组件注入测试

// tests/components/ThemeProvider.spec.ts
import { render, h } from '@testing-library/vue'
import { provideTheme, useTheme } from '@/contexts/themeContext'

const TestComponent = {
  setup() {
    const theme = useTheme()
    return () => h('div', theme.value.primaryColor)
  }
}

test('provides theme context', () => {
  const theme = { primaryColor: '#ff0000' }
  
  const { container } = render({
    setup() {
      provideTheme(ref(theme))
      return () => h(TestComponent)
    }
  })

  expect(container.textContent).toBe(theme.primaryColor)
})

八、企业级项目架构

1. 推荐目录结构

src/
├── composables/          # 自定义Hook
│   ├── useFetch.ts       # API请求Hook
│   ├── usePagination.ts  # 分页逻辑
│   └── index.ts          # 统一导出
├── contexts/             # 依赖注入上下文
│   ├── themeContext.ts   # 主题上下文
│   └── authContext.ts    # 认证上下文
├── lib/                  # 工具库
│   ├── debounce.ts       # 防抖函数
│   └── throttle.ts       # 节流函数
├── factories/            # 组件工厂
│   ├── FormFactory.tsx   # 动态表单
│   └── TableFactory.tsx  # 动态表格
└── hooks/                # 业务Hook
    ├── useUser.ts        # 用户相关
    └── useProducts.ts    # 商品相关

2. 类型定义管理

// types/hooks.d.ts
declare module '@/composables/useFetch' {
  export interface UseFetchOptions<T> {
    immediate?: boolean
    initialData?: T
    onSuccess?: (data: T) => void
    onError?: (error: Error) => void
  }

  export function useFetch<T>(
    url: Ref<string> | string,
    options?: UseFetchOptions<T>
  ): {
    data: Ref<T | undefined>
    error: Ref<Error | null>
    isFetching: Ref<boolean>
    isFinished: Ref<boolean>
    execute: () => Promise<void>
    refresh: () => Promise<void>
  }
}

九、常见问题解决方案

1. 循环引用问题

解决方案

// composables/useA.ts
import { useB } from './useB'

export function useA() {
  const { b } = useB()
  // ...
}

// composables/useB.ts
import { useA } from './useA'

let a: ReturnType<typeof useA>
export function useB() {
  if (!a) a = useA() // 延迟初始化
  
  return {
    // ...
  }
}

2. 内存泄漏处理

自动清理Hook

import { onUnmounted } from 'vue'

export function useAutoCleanup() {
  const cleanupCallbacks = new Set<() => void>()

  function autoCleanup(cb: () => void) {
    cleanupCallbacks.add(cb)
  }

  onUnmounted(() => {
    cleanupCallbacks.forEach(cb => cb())
    cleanupCallbacks.clear()
  })

  return { autoCleanup }
}

十、前沿探索

1. Reactivity Transform

// vite.config.js
export default {
  plugins: [
    vue({
      reactivityTransform: true
    })
  ]
}

// 使用$ref语法糖
let count = $ref(0) // 自动解包,无需.value

2. Suspense集成




通过深入理解和应用这些高级特性,开发者可以构建出更健壮、更易维护的Vue 3应用程序。Composition API与自定义Hooks的结合,为复杂前端应用提供了全新的开发范式,使得逻辑复用和代码组织达到了前所未有的灵活程度。

你可能感兴趣的:(vue,vue.js,ubuntu,前端)