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/