前言
Vue3组合式API是Vue3引入的一种新的API风格,它通过将组件的逻辑拆分为多个可重用的函数,使得组件的代码更加清晰、可维护和可测试。本文将介绍Vue3组合式API的最新实践,包括最佳实践、性能优化和未来发展趋势。
一、为什么组合式API是Vue3的革命性升级
1.1 选项式API的痛点
- 代码碎片化:数据在
data
,方法在 methods
,计算属性在computed
,生命周期钩子在 mounted
等选项中分散。
- 逻辑耦合:1000行组件中去找关联逻辑如同大海捞针。
- 可维护性差:Mixins存在命名冲突和来源不清晰问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| export default { data () { return { users: [], filters: {}, pagination: {} } }, methods: { fetchUsers () { }, deleteUsers () { }, handlePagination () { } }, computed: { filteredUsers () { } }, watch: { filters: { handler () { }, deep: true } } }
|
1.2 组合式API的三大优势
- 逻辑聚合:按功能而非选项组织代码
- 完美复用:函数式封装实现”即插即用”
- 类型支持:天然适配TypeScript
1 2 3 4 5 6 7 8 9 10 11 12
| import { useUserFetch } from './composables/userFetch' import { useTableFilter } from './composables/tableFilter' export default { setup() { const { users, fetchUsers } = useUserFetch() const { filteredData, filters } = useTableFilter(users) return { users, filteredData, filters, fetchUsers } } }
|

二、组合式API核心机制深度剖析(附完整代码)
2.1 setup函数:新世界的入口
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <button @click="increment">{{ count }}</button> </template>
<script setup> // 编译器语法糖 import { ref, onMounted } from 'vue' const count = ref(0) function increment() { count.value++ } </script>
|
关键细节:
- 执行时机:在
beforeCreate
之前
- 参数解析:
props
是响应式的,不用解构
- Context对象:包含
attrs
/ slots
/ emit
等
2.2 ref() vs reactive() 选择指南
场景 |
推荐方案 |
原因 |
基础类型数据 |
ref() |
自动解包,模板使用更方便 |
复杂对象/数组 |
reactive() |
深层响应式,性能更优 |
第三方类实例 |
reactive() |
保持原型链方法 |
跨组件状态共享 |
ref() + provide/inject |
响应式追踪更可控 |
ref的底层原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| export function ref(value?: unknown) { return createRef(value, false) }
export function shallowRef(value?: unknown) { return createRef(value, true) }
function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }
class RefImpl<T> { private _value: T private _rawValue: T
public dep?: Dep = undefined public readonly __v_isRef = true
constructor( value: T, public readonly __v_isShallow: boolean, ) { this._rawValue = __v_isShallow ? value : toRaw(value) this._value = __v_isShallow ? value : toReactive(value) }
get value() { trackRefValue(this) return this._value }
set value(newVal) { const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal) newVal = useDirectValue ? newVal : toRaw(newVal) if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) triggerRefValue(this, DirtyLevels.Dirty, newVal) } } }
|
ref是一个函数,它接收一个内部值并且返回一个响应式且可变的引用对象。这个引用对象有一个.value
属性,这个属性指向内部值。
reactive的底层原理
reactive
是一个函数,它接受一个对象并返回该对象的响应式代理,也就是proxy
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function reactive(target) { if (target && target.__v_isReactive) { return target }
return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) }
function createReactiveObject( target, isReadonly, baseHandlers, collectionHandlers, proxyMap ) { if (!isObject(target)) { return target } const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy }
const proxy = new Proxy(target, baseHandlers) proxyMap.set(target, proxy) return proxy }
|
reactive
的源码简单很多,reactive
通过new Proxy(target, baseHandlers)
创建了一个代理。这个代理会拦截对目标对象的操作,从而实现响应式。
具体的使用:
1 2 3 4
| import { reactive } from 'vue'
let state = reactive({ count: 0 }) state.count++
|
可以看出ref和reactive的区别:
- 底层原理不一样
ref
采用RefImpl
实现,reactive
则采用Proxy
实现
ref更深入的理解
当你使用 new RefImpl(value)
创建一个 RefImpl
实例时,实际上发生了以下几个步骤:
- 内部值:实例存储了传递给构造函数的初始值。
- 依赖收集:实例需要跟踪所有依赖于他的效果(effect),例如计算属性或者副作用函数。通常通过一个依赖列表或函数来实现。
- 触发更新:当实例的值发生变化时,它需要通知所有依赖它的效果进行更新,以便他们可以重新计算或执行。
RefImpl 类似于发布-订阅模式的设计,以下是一个简化的RefImpl
类的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Dep { constructor() { this.subscribers = new Set(); } depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } notify() { this.subscribers.forEach(effect => effect()); } }
let activeEffect = null;
function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; }
class RefImpl { constructor(value) { this._value = value; this.dep = new Dep(); } get value() { this.dep.depend(); return this._value; } set value(newValue) { if (newValue !== this._value) { this._value = newValue; this.dep.notify(); } } }
let count = new RefImpl(0);
watchEffect(() => { console.log('Effect 1:', count.value); }) count.value = 1;
|
Dep类负责管理一个依赖列表,并提供依赖收集和通知更新的功能;RefImpl类包含一个内部值_value和一个Dep实例。当value被访问时,通过get方法进行依赖收集;当value被赋予新值时,通过set方法触发更新。
reactive的局限性
Vue3中,reactive API 通过 proxy实现了一种响应式数据的方式,尽管这种方法在性能上比vue2有所提升,但proxy的局限性也导致了reactive的局限性,这些局限性会影响开发者的使用体验。
1.仅对引用数据类型有效
reactive 主要适用于对象,包括数组和一些集合类型(如:Map和Set)。对于基础数据类型(如:string、number、boolean),reactive 无法直接响应。
2.使用不当会失去响应
- 直接赋值对象,失去响应性
- 直接替换响应对象,失去响应性
- 直接解构对象,会失去响应性。解决:用toRefs函数来将响应式对象转换为ref对象
- 将响应式对象的属性赋值给变量,会失去响应式
高级实战技巧
3.1 通用数据请求封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export const useFetch = (url) => { const data = ref(null) const error = ref(null) const loading = ref(false)
const fetchData = async () => { try { loading.value = true const response = await axios.get(url) data.value = response.data } catch (err) { error.value = err } finally { loading.value = false } } onMounted(fetchData) return { data, error, loading, retry: fetchData } }
const { data: posts } = useFetch('/api/posts')
|
3.2 防抖探索实战
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export function useDebounceSearch(callback, delay=500) { const searchQuery = ref('') let timeoutId = null
watch(searchQuery, (newVal) => { clearTimeout(timeoutId) timeoutId = setTimeout(() => { callback(newVal) }, delay) })
return { searchQuery } }
|
性能优化最佳实践
4.1 计算属性缓存策略
1 2 3 4 5 6 7 8
| const filteredList = computed(() => { const cached = {} return (filterKey) => { if(cache[filterKey]) return cache[filterKey] return cache[filterKey] = heavyCompute() } })
|
4.2 watchEffect()的高级用法
1 2 3 4 5 6 7 8
| watchEffect(() => { const data = fetchData(params.value) console.log('依赖自动追踪:',data) }, { flush: 'post', onTrack(e) {} })
|
4.3 内催泄露防范
1 2 3 4 5
| onMounted(() => { const timer = setInterval(() => {...}, 1000) onUnmounted(() => clearInterval(timer)) })
|
TypeScript终级适配方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface User { name: string age: number, id: number }
const user = ref<User>({id: 1, name: 'Alice', age: 25})
export function useCounter(): { count: Ref<number> increment: () => void } { }
|
总结
如果你对这篇文章有任何疑问、建议或者独特的见解,欢迎在评论区留言。无论是探讨技术细节,还是分享项目经验,都能让我们共同进步。