Vue 3的Composition API通过setup函数和一组响应式API重新组织组件逻辑,解决Options API在复杂组件中逻辑分散的问题。Vue 3.5版本对响应式系统做了内部重构,依赖追踪采用双向链表结构替代原有的Set集合,内存占用降低约56%,大列表渲染性能显著提升。理解ref、reactive、computed和watch的底层机制是编写高质量自定义Hook和调试响应式问题的前提。
ref与reactive响应式代理机制对比
ref通过RefImpl类包装原始值,通过get value和set value拦截器实现依赖收集和触发。reactive通过Proxy代理整个对象,递归拦截属性访问。两者的核心差异在于ref适用于原始类型和需要整体替换的引用类型,reactive适用于需要保持引用稳定性的对象。
// ref与reactive的实现差异演示
import { ref, reactive, watchEffect } from 'vue'
// ref:包装原始值,访问需要.value
const count = ref(0)
console.log(count.value) // 0
count.value++ // 触发依赖更新
// reactive:代理对象,直接访问属性
const state = reactive({ name: 'Vue', version: 3 })
state.version = 3.5 // 触发依赖更新
// reactive的陷阱:解构丢失响应性
const { name } = state // name不再是响应式
// 解决方案:使用toRefs
import { toRefs } from 'vue'
const { name: reactiveName } = toRefs(state) // reactiveName保持响应性
// ref的模板自动解包:在template中不需要.value
// <div>{{ count }}</div> 正确,不需要count.value
// 深层响应:reactive自动递归代理嵌套对象
const nested = reactive({
user: { profile: { age: 25 } }
})
nested.user.profile.age = 26 // 触发更新
Vue 3.5引入了ReactiveEffect双向链表优化,每个副作用函数和维护其依赖的dep对象直接关联,避免Set遍历开销。当watchEffect或computed的回调执行时,被访问的ref/reactive属性自动将当前Effect注册为依赖;当属性变更时,通过dep直接定位并执行关联的Effect,查找复杂度从O(n)降至O(1)。
computed缓存机制与watch回调时机
computed创建时返回ComputedRefImpl实例,内部维护_dirty标志位控制缓存。首次访问时执行getter计算并缓存结果,同时将自身注册为依赖项的订阅者。依赖变更时设置_dirty为true,下次访问才重新计算,实现惰性求值。
// computed缓存与watch配置详解
import { ref, computed, watch } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// computed缓存:依赖未变时不重新计算
const fullName = computed(() => {
console.log('computed executed') // 仅在依赖变化后首次访问时打印
return `${firstName.value} ${lastName.value}`
})
console.log(fullName.value) // 打印"computed executed",返回"John Doe"
console.log(fullName.value) // 不打印,返回缓存值"John Doe"
firstName.value = 'Jane'
console.log(fullName.value) // 打印"computed executed",返回"Jane Doe"
// watch选项配置
const searchText = ref('')
// flush: 'pre'(默认)在组件更新前执行,'post'在更新后,'sync'同步执行
watch(searchText, (newVal, oldVal, onCleanup) => {
const controller = new AbortController()
fetch(`/api/search?q=${newVal}`, { signal: controller.signal })
.then(res => res.json())
.then(data => console.log(data))
// onCleanup注册清理函数,下次回调前自动执行
onCleanup(() => controller.abort())
}, {
immediate: true, // 首次执行时立即触发回调
deep: false, // 深度监听(仅对reactive对象有效)
flush: 'pre', // 回调执行时机
})
// watchEffect:自动收集依赖,无需显式声明
watchEffect(async (onCleanup) => {
// 自动追踪searchText.value作为依赖
const data = await fetchData(searchText.value)
// 副作用逻辑
})
watch的flush: ‘pre’选项在大多数场景下是正确的选择,DOM更新前执行回调避免布局抖动。需要访问更新后的DOM时使用flush: ‘post’。flush: ‘sync’在依赖变更后立即同步执行,可能引发性能问题,仅在确有需求时使用。
自定义Hook设计模式与复用实践
自定义Hook本质是复用响应式逻辑的函数,命名以use开头,返回ref或包含ref的普通对象。设计原则包括:保持Hook职责单一、所有响应式状态通过返回值暴露、副作用在Hook内部通过onMounted等生命周期钩子管理。
// 自定义Hook:通用分页逻辑复用
import { ref, computed, watch, readonly } from 'vue'
export function usePagination(fetchFn, options = {}) {
const currentPage = ref(options.defaultPage ?? 1)
const pageSize = ref(options.defaultSize ?? 20)
const total = ref(0)
const data = ref([])
const loading = ref(false)
const error = ref(null)
const totalPages = computed(() =>
Math.ceil(total.value / pageSize.value)
)
const hasNext = computed(() => currentPage.value < totalPages.value)
const hasPrev = computed(() => currentPage.value > 1)
async function loadPage() {
loading.value = true
error.value = null
try {
const result = await fetchFn({
page: currentPage.value,
size: pageSize.value,
})
data.value = result.items
total.value = result.total
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
function nextPage() {
if (hasNext.value) {
currentPage.value++
loadPage()
}
}
function prevPage() {
if (hasPrev.value) {
currentPage.value--
loadPage()
}
}
// 监听分页参数变化自动加载
watch([currentPage, pageSize], loadPage, { immediate: true })
return {
currentPage: readonly(currentPage),
pageSize: readonly(pageSize),
total: readonly(total),
totalPages,
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
hasNext,
hasPrev,
nextPage,
prevPage,
loadPage,
}
}
// 组件中使用
// const { data, loading, nextPage, hasNext } = usePagination(fetchUsers, {
// defaultPage: 1, defaultSize: 10
// })
上述usePagination Hook封装了分页状态管理和数据加载逻辑,组件只需提供fetchFn即可获得完整的分页能力。返回值使用readonly包裹ref防止外部直接修改内部状态,所有状态变更通过暴露的方法执行。这种设计模式在表格、列表、瀑布流等场景可直接复用,显著减少组件内的重复逻辑。
自定义Hook组合能力是Composition API的核心优势。usePagination可以与useDebounce Hook组合实现搜索防抖分页,与useIntersectionObserver Hook组合实现无限滚动分页。每个Hook保持独立测试能力,不依赖组件上下文,单元测试更简洁。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue35compositionapi-xiang-ying-shi-yuan-li-yu-zi-ding-yi/