Vue3的组合式API(Composition API)改变了组件逻辑的组织方式,解决了Options API在复杂组件中逻辑分散的问题。本文通过代码示例剖析响应式系统原理,并演示如何设计可复用的组合式函数。
响应式系统核心:ref与reactive的实现差异
Vue3提供两种响应式API:ref和reactive。reactive基于Proxy实现,对对象进行深度代理,访问和修改属性时自动触发依赖追踪和更新。ref通过包装值类型并拦截value属性的get和set实现响应式。
reactive的实现原理:
// Vue3 reactive简化实现
const reactiveMap = new WeakMap()
function reactive(target) {
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
const proxy = new Proxy(target, {
get(obj, key, receiver) {
track(obj, key) // 依赖收集
const result = Reflect.get(obj, key, receiver)
if (typeof result === 'object' && result !== null) {
return reactive(result) // 深度响应式
}
return result
},
set(obj, key, value, receiver) {
const result = Reflect.set(obj, key, value, receiver)
trigger(obj, key) // 触发更新
return result
}
})
reactiveMap.set(target, proxy)
return proxy
}
ref的简化实现:
function ref(value) {
return {
get value() {
track(this, 'value')
return value
},
set value(newVal) {
if (newVal !== value) {
value = newVal
trigger(this, 'value')
}
}
}
}
选择原则:基本类型用ref,对象类型用reactive。模板中ref自动解包,脚本中需要通过.value访问。
组合式函数设计:状态管理与逻辑复用
组合式函数(Composables)是Vue3逻辑复用的核心模式。以一个分页数据加载组合式函数为例:
// composables/usePagination.js
import { ref, reactive, computed } from 'vue'
export function usePagination(fetchFn, options = {}) {
const {
pageSize = 20,
initialPage = 1
} = options
const state = reactive({
data: [],
total: 0,
currentPage: initialPage,
loading: false,
error: null
})
const totalPages = computed(() =>
Math.ceil(state.total / pageSize)
)
const hasNext = computed(() => state.currentPage < totalPages.value)
const hasPrev = computed(() => state.currentPage > 1)
async function loadData() {
state.loading = true
state.error = null
try {
const result = await fetchFn({
page: state.currentPage,
pageSize
})
state.data = result.list
state.total = result.total
} catch (err) {
state.error = err.message
state.data = []
} finally {
state.loading = false
}
}
async function nextPage() {
if (hasNext.value) {
state.currentPage++
await loadData()
}
}
async function prevPage() {
if (hasPrev.value) {
state.currentPage--
await loadData()
}
}
async function goToPage(page) {
if (page >= 1 && page <= totalPages.value) {
state.currentPage = page
await loadData()
}
}
return {
state,
totalPages,
hasNext,
hasPrev,
loadData,
nextPage,
prevPage,
goToPage
}
}
在组件中使用:
<template>
<div class="table-container">
<el-table :data="state.data" v-loading="state.loading">
<el-table-column prop="name" label="名称" />
<el-table-column prop="status" label="状态" />
</el-table>
<el-pagination
v-model:current-page="state.currentPage"
:page-size="20"
:total="state.total"
@current-change="goToPage"
/>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { usePagination } from '@/composables/usePagination'
import { fetchUserList } from '@/api/user'
const {
state,
loadData,
goToPage
} = usePagination(fetchUserList, { pageSize: 20 })
onMounted(() => loadData())
</script>
跨端组件设计:响应式布局适配方案
Vue3配合CSS变量和useMediaQuery组合式函数实现跨端响应式布局:
// composables/useMediaQuery.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMediaQuery(query) {
const matches = ref(false)
let mediaQuery
const update = (e) => {
matches.value = e.matches
}
onMounted(() => {
mediaQuery = window.matchMedia(query)
matches.value = mediaQuery.matches
mediaQuery.addEventListener('change', update)
})
onUnmounted(() => {
mediaQuery?.removeEventListener('change', update)
})
return matches
}
// 使用示例
const isMobile = useMediaQuery('(max-width: 768px)')
const isTablet = useMediaQuery('(max-width: 1024px)')
组件中根据断点动态渲染不同布局:
<template>
<div :class="['layout', { 'layout--mobile': isMobile }]">
<sidebar v-if="!isMobile" />
<main-content />
<mobile-nav v-if="isMobile" />
</div>
</template>
watchEffect与watch的使用场景区分
watchEffect自动收集依赖并立即执行,适合副作用与多个响应式数据关联的场景。watch需要明确指定监听源,可以访问旧值,适合需要精确控制执行时机的场景。
// watchEffect:自动追踪依赖
watchEffect(() => {
console.log(state.currentPage, state.pageSize)
// 当currentPage或pageSize变化时自动重新执行
})
// watch:精确控制,可获取旧值
watch(
() => state.currentPage,
(newPage, oldPage) => {
if (newPage !== oldPage) {
loadData()
}
},
{ flush: 'post' } // DOM更新后执行
)
flush选项设为post确保在DOM更新后执行回调,避免操作到未更新的DOM节点。immediate选项设为true可以在初始化时立即执行一次。
性能优化:shallowRef与markRaw的应用
处理大列表或不需要深度响应式的数据时,shallowRef和markRaw能减少不必要的代理开销:
import { shallowRef, markRaw } from 'vue'
// 大列表数据使用shallowRef,只对.value赋值触发更新
const bigList = shallowRef([])
bigList.value = await fetchHugeDataset()
// 第三方类实例使用markRaw跳过响应式代理
const chartInstance = markRaw(echarts.init(domEl))
前端工程化实践中,合理使用这些API可以显著降低内存占用和渲染开销,尤其在数据量大的管理后台场景中效果明显。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-shen-du-shi-zhan-xiang-ying-shi-yuan-li/