Vue3组合式API解决了什么问题
Vue2的选项式API在大型组件中暴露出明显的组织缺陷——一个功能的数据、计算属性、方法和生命周期钩子分散在不同选项中,一个需求变更需要在多个选项之间跳转修改。组合式API用setup函数(或<script setup>语法糖)将同一功能的逻辑聚合在一起,可读性和可维护性大幅提升。
这篇文章从响应式原理讲起,覆盖组合式API核心用法、自定义Hook封装模式和大型项目的Pinia状态管理方案。
响应式系统:ref与reactive的选择
Vue3响应式基于Proxy实现。ref和reactive两种声明方式各有适用场景:
import { ref, reactive } from 'vue'
// ref:基础类型和需要重新赋值的对象
const count = ref(0)
const user = ref({ name: '张三', age: 28 })
// reactive:不需要重新赋值的复杂对象
const form = reactive({
username: '',
password: '',
remember: false
})
选择原则:
– 基础类型(string/number/boolean)必须用ref
– 对象如果整体替换(=新对象),用ref
– 对象只修改属性不整体替换,用reactive,减少模板中的.value书写
reactive的解构陷阱是常见Bug来源:
// 错误:解构后失去响应式
const { username, password } = form // 响应式丢失!
// 正确:用toRefs保持响应式
import { toRefs } from 'vue'
const { username, password } = toRefs(form) // 保持响应式
computed与watch的工程化用法
computed用于派生状态,watch用于副作用响应。两者的核心区别是computed有缓存,watch没有返回值。
import { computed, watch, ref } from 'vue'
const productList = ref<Product[]>([])
const keyword = ref('')
// computed:派生过滤后的列表
const filteredList = computed(() => {
return productList.value.filter(item =>
item.name.includes(keyword.value)
)
})
// watch:搜索关键词变化时请求接口
watch(keyword, async (newVal, oldVal) => {
if (newVal.length < 2) return // 防止短关键词触发
const { data } = await searchProducts(newVal)
productList.value = data
}, {
immediate: false, // 是否立即执行一次
flush: 'post', // DOM更新后执行
})
watch监听多个源时,避免写多个watch,用一个watch监听数组:
watch([keyword, category, sortBy], async () => {
const params = {
keyword: keyword.value,
category: category.value,
sort: sortBy.value
}
const { data } = await fetchProducts(params)
productList.value = data
})
watchEffect是watch的简化版,自动追踪依赖:
// 自动追踪内部使用的所有响应式变量
watchEffect(async () => {
// keyword或category变化都会触发
const { data } = await fetchProducts({
keyword: keyword.value,
category: category.value
})
productList.value = data
})
注意watchEffect无法获取旧值,也无法条件跳过执行,适用于不需要旧值的场景。
自定义Hook:逻辑复用的核心模式
自定义Hook是Vue3中逻辑复用的推荐方式,替代Vue2的Mixin。Hook的命名以use开头:
// usePagination.ts
import { ref, computed, reactive, readonly } from 'vue'
interface PaginationState {
page: number
pageSize: number
total: number
}
export function usePagination(defaultPageSize = 10) {
const state = reactive<PaginationState>({
page: 1,
pageSize: defaultPageSize,
total: 0
})
const totalPages = computed(() =>
Math.ceil(state.total / state.pageSize)
)
const hasNext = computed(() => state.page < totalPages.value)
const hasPrev = computed(() => state.page > 1)
function goPage(page: number) {
if (page < 1 || page > totalPages.value) return
state.page = page
}
function setTotal(total: number) {
state.total = total
// 数据减少时自动修正当前页
if (state.page > totalPages.value && totalPages.value > 0) {
state.page = totalPages.value
}
}
return {
state: readonly(state), // 只读暴露,防止外部直接修改
totalPages,
hasNext,
hasPrev,
goPage,
setTotal
}
}
组件中使用:
import { usePagination } from '@/hooks/usePagination'
const { state, totalPages, goPage, setTotal } = usePagination(20)
// 请求列表数据
async function fetchList() {
const { data, total } = await api.getList({
page: state.page,
pageSize: state.pageSize
})
setTotal(total)
list.value = data
}
Pinia状态管理:大型项目的数据流方案
大型项目中跨组件状态共享用Pinia替代Vuex。Pinia的Store分两种:
Option Store(类似Vuex写法):
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || '',
userInfo: null as UserInfo | null,
}),
getters: {
isLoggedIn: (state) => !!state.token,
},
actions: {
async login(credentials: LoginForm) {
const { token, user } = await api.login(credentials)
this.token = token
this.userInfo = user
localStorage.setItem('token', token)
},
logout() {
this.token = ''
this.userInfo = null
localStorage.removeItem('token')
}
}
})
Setup Store(组合式写法,更灵活):
// stores/app.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAppStore = defineStore('app', () => {
const sidebarCollapsed = ref(false)
const theme = ref<'light' | 'dark'>('light')
const themeClass = computed(() => `theme-${theme.value}`)
function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value
}
function switchTheme(newTheme: 'light' | 'dark') {
theme.value = newTheme
document.documentElement.setAttribute('data-theme', newTheme)
}
return {
sidebarCollapsed,
theme,
themeClass,
toggleSidebar,
switchTheme
}
})
Store之间组合调用:
export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore()
async function checkout() {
if (!userStore.isLoggedIn) {
throw new Error('请先登录')
}
// 结算逻辑...
}
})
项目工程化配置清单
Vue3大型项目的工程化需要配置以下工具链:
– TypeScript:启用strict模式,类型覆盖所有props和emit
– ESLint + Prettier:统一代码风格,配合husky在提交前自动格式化
– UnoCSS:原子化CSS方案,比Tailwind更轻量,按需生成样式
– Vite:开发服务器启动速度快,HMR响应在100ms以内
– VueRouter:路由守卫做权限控制,懒加载减少首屏体积
组合式API不是银弹,但它解决了Vue2选项式API在大型项目中逻辑分散的核心痛点。配合TypeScript和Pinia,Vue3项目可以支撑百万行级的代码库规模。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-shi-zhan-cong-xiang-ying-shi-yuan-li-dao/