刚开始接触Vue3组合式API的时候,我心里想的是:这不就是换个写法吗?结果真上了项目才发现,坑多到能写一本《组合式API踩坑指南》。今天我把这三个月的实战经验掰开了揉碎了讲给你听,保证每一行代码都是血泪教训换来的。
开篇配图:一张Vue3组合式API代码截图,旁边标注“踩坑记录:第37次”,营造实战氛围。
先看第一个坑:你还在用选项式API的思维写组合式API吗?
很多人从Vue2过来,习惯性地把setup当成created来用。我一开始也是这样,结果写出来的组件比选项式还乱。
为什么要这么写:组合式API的核心是逻辑复用,不是简单的代码搬家。
看一个反面教材:
“javascript
// 错误示范:还是在用选项式思维
export default {
setup() {
// 所有逻辑堆在一起
const userList = ref([])
const loading = ref(false)
const fetchUsers = async () => {
loading.value = true
const res = await api.getUsers()
userList.value = res.data
loading.value = false
}
// 这里又混入了搜索逻辑
const searchText = ref('')
c
<
p>onst filteredUsers = computed(() => ...)
return { userList, loading, fetchUsers, searchText, filteredUsers }
}
}
`
这里有个细节:ref和reactive的选择。我踩过的坑告诉你,基本数据类型用ref,复杂嵌套用reactive?不对!真实项目里,我建议统一用ref。为什么?因为reactive的解构会丢失响应性,这个坑我吃了三次才长记性。
核心技巧:composable才是组合式API的精髓
刚开始我也以为composable就是提取公共函数,结果写出来的东西跟工具函数没啥区别。
为什么要这么写:composable要管理自己的生命周期和状态,而不是无状态的函数。
看一个真实的搜索组件优化:
`javascript
// useSearch.js - 这才是正确姿势
import { ref, computed, watch, onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
export function useSearch(fetchFunction, options = {}) {
const { debounceTime = 300 } = options
const keyword = ref('')
const results = ref([])
const isLoading = ref(false)
const error = ref(null)
// 使用debounce避免频繁请求
const debouncedSearch = debounce(async (kw) => {
if (!kw.trim()) {
results.value = []
/> return
}
isLoading.value = true
error.value = null
try {
const data = await fetchFunction(kw)
results.value = data
} catch (e) {
error.value = e.message
} finally {
isLoading.value = false
}
}, debounceTime)
// watch自动触发搜索
watch(keyword, (newVal) => {
debouncedSearch(newVal)
})
// 清理定时器
onUnmounted(() => {
debouncedSearch.cancel()
})
return {
keyword,
results,
isLoading,
error
}
}
`
这个设计真的反人类?一开始我也觉得复杂,但用上之后,搜索逻辑从200行缩到了40行,组件直接清爽了。
核心配图:一张对比图,左侧是混乱的setup代码,右侧是模块化的composable,用红框标出关键改进点。
另一个坑:watch和watchEffect,选错一个性能差10倍
这个坑我整整踩了一周。项目有个实时监控面板,用了watch后页面卡得不行,CPU占用直接飙到80%。换watchEffect后,从3.2秒降到0.8秒。
为什么要这么写:watchEffect会自动追踪依赖,适合副作用场景;watch适合需要具体新旧值的场景。
`javascript
// 性能杀手版 - 用watch监听大量数据
import { watch, ref } from 'vue'
const data = ref([])
// 每次data变化都触发,即使只是某个字段变了
watch(data, (newVal) => {
updateChart(newVal)
}, { deep: true })
// 优化版 - 用watchEffect自动追踪
import { watchEffect, ref } from 'vue'
const data = ref([])
// 只有在updateChart内部实际用到的数据变化时才触发
watchEffect(() => {
// Vue会自动追踪这里用到的响应式数据
updateChart(data.value.filter(item => item.active))
})
`
这里有个官方文档没写的技巧:watchEffect的清理函数。如果副作用需要中断(比如取消请求),可以这样:`javascript
watchEffect((onCleanup) => {
const controller = new AbortController()
fetchData(controller.signal)
onCleanup(() => {
controller.abort() // 组件卸载或重新执行时自动取消
})
})
`
还有一个技巧:模板引用别再手动管理了
Vue2时代我们习惯用this.$refs,Vue3里用ref绑定模板引用,但很多人忽略了函数式引用的威力。
为什么要这么写:函数式引用可以拿到完整的生命周期,适合复杂的DOM操作。
`javascript
// 基础版
<script setup>
import { ref, onMounted } from 'vue'
const myRef = ref(null)
onMounted(() => {
console.log(myRef.value) // 能拿到
})
</script>
// 进阶版 - 函数式引用,适合列表渲染
{{ item.name }}
<script setup>
import { reactive, onMounted } from 'vue'
const itemRefs = reactive({})
onMounted(() => {
// 直接通过ID访问
console.log(itemRefs['123'])
})
</script>
`
这个设计真的反人类?刚开始我也觉得不如$refs方便,但用过之后发现,函数式引用能精确控制每个元素,尤其在虚拟滚动这种场景下,性能提升巨大。
总结前配图:一张Vue3组合式API最佳实践清单思维导图,包括composable设计、性能优化、错误处理等核心模块。
总结:你可以立刻用的三个点
管理状态,放弃reactive,避免解构丢失响应性的坑。如果非要reactive,记得用toRefs包裹再解构。。一个组件最好不超过3个composable,每个不超过100行。替代watch`,除非你需要具体的新旧值做比较。性能差异在数据量大时特别明显,实测从3.2秒降到0.8秒不是吹的。最后说一句:Vue3组合式API的学习曲线确实陡,但熬过前两周,你会发现它比选项式API灵活十倍。遇到坑别慌,多看看源码,官方文档虽然文档不够清晰,但源码不会骗人。