Vue3组合式API实战:响应式原理剖析与自定义Hook封装最佳实践

Vue3组合式API(Composition API)是Vue3最重要的特性之一,通过setup函数和一系列响应式API,将组件逻辑按功能组织而非按选项类型组织,解决了Options API在复杂组件中逻辑分散的问题。ref、reactive、computed、watch等API构成了响应式系统的核心,自定义Hook(Composable)则将可复用逻辑从组件中抽离。本文从响应式原理到自定义Hook封装,演示Vue3组合式API的完整开发流程。

Vue3响应式系统原理:Proxy与依赖收集

Vue2使用Object.defineProperty实现响应式,存在无法检测属性新增/删除和数组索引修改的问题。Vue3改用ES6 Proxy实现,对整个对象进行代理,能拦截所有属性操作。

响应式系统的核心流程:读取响应式数据时触发track(依赖收集),将当前effect函数收集到该属性的依赖集合中;修改响应式数据时触发trigger(派发更新),遍历该属性的依赖集合重新执行所有effect函数。

// 简化版响应式原理
const targetMap = new WeakMap()
let activeEffect = null

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) targetMap.set(target, (depsMap = new Map()))
  let dep = depsMap.get(key)
  if (!dep) depsMap.set(key, (dep = new Set()))
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) dep.forEach(effect => effect())
}

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      track(obj, key)
      return Reflect.get(obj, key)
    },
    set(obj, key, value) {
      Reflect.set(obj, key, value)
      trigger(obj, key)
      return true
    }
  })
}

ref与reactive使用场景与选择

ref用于基本类型(string、number、boolean),reactive用于对象和数组。ref通过.value访问,reactive直接访问属性。ref内部对对象类型也使用reactive代理。

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

// ref:基本类型
const count = ref(0)
console.log(count.value) // 0
count.value++

// reactive:对象/数组
const state = reactive({
  user: { name: '张三', age: 25 },
  list: []
})
state.user.name = '李四' // 直接修改,响应式触发

// computed:计算属性
const doubleCount = computed(() => count.value * 2)

// watch:侦听器
watch(count, (newVal, oldVal) => {
  console.log(`count从${oldVal}变为${newVal}`)
})

// watch侦听reactive对象的某个属性
watch(() => state.user.age, (newVal) => {
  console.log(`年龄变为${newVal}`)
})

setup语法糖与script setup用法

script setup是Vue3.2+引入的编译时语法糖,简化了setup函数的写法,组件内变量和函数自动暴露给模板:

<template>
  <div>
    <p>计数: {{ count }}</p>
    <p>双倍: {{ doubleCount }}</p>
    <button @click="increment">+1</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

function increment() {
  count.value++
}

function reset() {
  count.value = 0
}
</script>

自定义Hook(Composable)封装模式

自定义Hook以use开头命名,封装可复用的响应式逻辑。规范的自定义Hook应返回响应式数据和操作方法,确保在多个组件中使用时状态独立。

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0, step = 1) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  
  function increment() {
    count.value += step
  }
  
  function decrement() {
    count.value -= step
  }
  
  function reset() {
    count.value = initialValue
  }
  
  return { count, double, increment, decrement, reset }
}

异步数据加载Hook:useFetch封装

// composables/useFetch.js
import { ref, watch, isRef, unref } from 'vue'

export function useFetch(url, options = {}) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  async function doFetch() {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(unref(url), options)
      if (!response.ok) throw new Error(`HTTP ${response.status}`)
      data.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      loading.value = false
    }
  }

  // 如果url是ref,监听变化自动重新请求
  if (isRef(url)) {
    watch(url, doFetch)
  }

  doFetch()

  return { data, error, loading, refresh: doFetch }
}

表单验证Hook:useFormValidation封装

// composables/useFormValidation.js
import { reactive, computed } from 'vue'

export function useFormValidation(initialValues, rules) {
  const values = reactive({ ...initialValues })
  const errors = reactive({})
  const touched = reactive({})

  function validateField(field) {
    const rule = rules[field]
    if (!rule) return true
    
    const value = values[field]
    for (const validator of rule) {
      const result = validator(value)
      if (result !== true) {
        errors[field] = result
        return false
      }
    }
    errors[field] = ''
    return true
  }

  function validateAll() {
    let valid = true
    for (const field in rules) {
      if (!validateField(field)) valid = false
    }
    return valid
  }

  function touch(field) {
    touched[field] = true
    validateField(field)
  }

  function resetForm() {
    Object.assign(values, initialValues)
    Object.keys(errors).forEach(k => errors[k] = '')
    Object.keys(touched).forEach(k => touched[k] = false)
  }

  const isValid = computed(() => Object.values(errors).every(e => !e))

  return { values, errors, touched, isValid, validateField, validateAll, touch, resetForm }
}

// 内置验证器
export const validators = {
  required: (msg = '此项必填') => (v) => v ? true : msg,
  minLength: (n, msg) => (v) => v && v.length >= n ? true : (msg || `最少${n}个字符`),
  email: (msg = '邮箱格式不正确') => (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) ? true : msg,
  phone: (msg = '手机号格式不正确') => (v) => /^1[3-9]\d{9}$/.test(v) ? true : msg
}

组合式API性能优化:shallowRef与watchEffect

对于大型对象,reactive会递归代理所有嵌套属性,带来性能开销。使用shallowRef只代理第一层,适合大列表数据。watchEffect自动收集依赖,无需显式声明侦听源。

import { shallowRef, watchEffect, triggerRef } from 'vue'

// shallowRef:只代理.value,不递归
const largeList = shallowRef([])

// 修改深层属性不会触发更新,需手动trigger
function updateItem(index, newData) {
  largeList.value[index] = newData
  triggerRef(largeList)
}

// watchEffect:自动收集依赖
watchEffect(() => {
  // 内部引用的响应式数据变化时自动执行
  console.log(`当前数量: ${largeList.value.length}`)
})

// 停止侦听
const stop = watchEffect(() => {
  console.log(count.value)
})
stop() // 不再侦听

Vue3组合式API的设计目标是逻辑复用和类型推导。通过自定义Hook将状态逻辑与视图分离,同一逻辑可在多个组件中复用而不产生冲突。ref/reactive/computed/watch四个API覆盖了绝大多数响应式场景,shallowRef和markRaw用于处理性能敏感的大对象。实际开发中,将每个业务功能封装为独立的Composable文件,按功能而非按生命周期组织代码,是组合式API的最佳实践。

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

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

相关推荐