Vue3组合式API深度实战:响应式系统原理与自定义Hook设计

Vue3组合式API(Composition API)是对Options API的根本性重构,解决了大型组件中逻辑分散、复用困难的问题。通过setup函数和响应式API,开发者能按功能而非选项类型组织代码。本文从响应式原理到自定义Hook设计,覆盖Vue3生态中组合式API的实际工程用法。

响应式系统核心:ref与reactive对比

Vue3的响应式系统基于Proxy实现,替代了Vue2的Object.defineProperty方案。ref和reactive是两种创建响应式数据的基础API,使用场景有别。

import { ref, reactive, computed, watch } from 'vue'

// ref用于基本类型
const count = ref(0)
console.log(count.value) // 访问需.value
count.value++ // 修改需.value

// reactive用于对象和数组
const state = reactive({
  user: { name: '张三', age: 28 },
  list: ['Vue3', 'React', 'Angular'],
  filter: 'all'
})

// reactive对象直接访问,无需.value
console.log(state.user.name)
state.list.push('Svelte')

// 使用toRefs解构reactive保持响应性
import { toRefs } from 'vue'
const { user, list } = toRefs(state)

reactive的局限在于无法直接解构——解构后失去响应性,需要用toRefs包装。ref虽然没有这个限制,但模板中会自动解包,脚本中需要手动.value。实际开发中,对象状态用reactive,计数器、布尔开关等基本类型用ref,这样区分清晰。

computed计算属性与watch侦听器

computed创建缓存计算值,只有依赖变化时才重新计算。watch侦听特定响应式数据的变化,执行副作用。

import { ref, reactive, computed, watch, watchEffect } from 'vue'

const products = reactive([
  { name: 'MacBook Pro', price: 14999, stock: 15 },
  { name: 'iPad Air', price: 4799, stock: 0 },
  { name: 'AirPods Pro', price: 1899, stock: 42 }
])

const filterInStock = ref(false)

// computed自动追踪依赖
const filteredProducts = computed(() => {
  if (filterInStock.value) {
    return products.filter(p => p.stock > 0)
  }
  return products
})

const totalPrice = computed(() => 
  filteredProducts.value.reduce((sum, p) => sum + p.price * p.stock, 0)
)

// watch监听特定数据
watch(
  () => products.length,
  (newLen, oldLen) => {
    console.log(`商品数量变化: ${oldLen} -> ${newLen}`)
  }
)

// watchEffect自动收集依赖
watchEffect(() => {
  console.log(`当前筛选结果: ${filteredProducts.value.length}件商品`)
})

computed与methods的区别在于缓存机制——computed在依赖未变化时返回缓存值,methods每次调用都重新执行。watch的lazy特性意味着默认不在初始化时执行,需要立即执行时设置immediate: true。

自定义组合式函数:逻辑复用的标准模式

自定义Hook(组合式函数)是Vue3中逻辑复用的推荐方式。命名以use开头,返回响应式数据和方法。相比Vue2的mixin,组合式函数具有明确的输入输出,不存在命名冲突和来源不透明的问题。

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

// 自定义Hook:窗口尺寸监听
export function useWindowSize() {
  const width = ref(window.innerWidth)
  const height = ref(window.innerHeight)

  const update = () => {
    width.value = window.innerWidth
    height.value = window.innerHeight
  }

  onMounted(() => {
    window.addEventListener('resize', update)
  })

  onUnmounted(() => {
    window.removeEventListener('resize', update)
  })

  return { width, height }
}

// 自定义Hook:防抖搜索
import { ref, watch } from 'vue'

export function useDebounce(initialValue, delay = 300) {
  const value = ref(initialValue)
  const debounced = ref(initialValue)
  let timer = null

  watch(value, (newVal) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      debounced.value = newVal
    }, delay)
  })

  const cancel = () => {
    clearTimeout(timer)
  }

  return { value, debounced, cancel }
}

使用自定义Hook的组件代码简洁清晰:

<script setup>
import { useWindowSize, useDebounce } from './composables'

const { width, height } = useWindowSize()
const { value: keyword, debounced: debouncedKeyword } = useDebounce('', 500)
</script>

<template>
  <div>
    <p>窗口尺寸: {{ width }} × {{ height }}</p>
    <input v-model="keyword" placeholder="搜索..." />
    <p>实际搜索词: {{ debouncedKeyword }}</p>
  </div>
</template>

响应式布局与组件库设计中的响应式

在前端工程化实践中,响应式系统需要配合CSS媒体查询实现响应式布局。Vue3的响应式数据可以动态控制布局类名:

import { ref, computed } from 'vue'

const screenWidth = ref(window.innerWidth)

const layoutClass = computed(() => {
  if (screenWidth.value < 768) return 'layout-mobile'
  if (screenWidth.value < 1200) return 'layout-tablet'
  return 'layout-desktop'
})

const isMobile = computed(() => screenWidth.value < 768)

组件库设计中,响应式数据传递遵循单向数据流原则。父组件通过props传递数据,子组件通过emit通知变更。对于跨层级共享状态,使用provide/inject而非层层传递props:

import { provide, inject, reactive, readonly } from 'vue'

// 父组件
const themeConfig = reactive({
  primaryColor: '#1890ff',
  mode: 'light'
})
provide('theme', readonly(themeConfig))

// 子组件(任意深度)
const theme = inject('theme')
// theme是只读的,子组件不能直接修改
// 通过provide的方法修改
const setTheme = inject('setTheme')
setTheme({ primaryColor: '#52c41a' })

TypeScript实战:组合式API类型推导

Vue3的TypeScript支持在组合式API中表现优秀。ref和reactive会自动推导类型,复杂场景可手动标注泛型。

import { ref, reactive, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
  roles: string[]
}

// ref泛型标注
const currentUser = ref<User | null>(null)

// reactive自动推导
const form = reactive<Partial<User>>({
  name: '',
  email: ''
})

// computed返回类型自动推导
const isAdmin = computed(() => 
  currentUser.value?.roles.includes('admin') ?? false
)

// 自定义Hook的泛型支持
function useFetch<T>(url: string) {
  const data = ref<T | null>(null)
  const error = ref<string | null>(null)
  const loading = ref(false)

  const execute = async () => {
    loading.value = true
    try {
      const res = await fetch(url)
      data.value = await res.json()
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unknown error'
    } finally {
      loading.value = false
    }
  }

  return { data, error, loading, execute }
}

泛型组合式函数配合TypeScript的窄化(narrowing)能实现编译期类型安全。在Web性能优化层面,合理的响应式数据拆分能减少不必要的重渲染——将不常变化的数据与频繁变化的数据分离,避免大范围依赖触发。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-shen-du-shi-zhan-xiang-ying-shi-xi-tong/

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

相关推荐