- 发布于
Vue 3 Composition API 完全指南:响应式编程新范式
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Vue 3 Composition API 完全指南:响应式编程新范式
Vue 3 引入的 Composition API 为我们提供了一种全新的组件逻辑组织方式。本文将深入探讨 Composition API 的核心概念、使用方法和最佳实践。
为什么需要 Composition API?
Options API 的局限性
在 Vue 2 中,我们使用 Options API 来组织组件逻辑:
// Vue 2 Options API
export default {
data() {
return {
count: 0,
user: null,
loading: false,
}
},
computed: {
doubleCount() {
return this.count * 2
},
},
methods: {
increment() {
this.count++
},
async fetchUser() {
this.loading = true
try {
this.user = await api.getUser()
} finally {
this.loading = false
}
},
},
mounted() {
this.fetchUser()
},
}
当组件变得复杂时,相关的逻辑会分散在不同的选项中,难以维护。
Composition API 的优势
// Vue 3 Composition API
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
// 计数器逻辑
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
// 用户数据逻辑
const user = ref(null)
const loading = ref(false)
const fetchUser = async () => {
loading.value = true
try {
user.value = await api.getUser()
} finally {
loading.value = false
}
}
onMounted(fetchUser)
return {
count,
doubleCount,
increment,
user,
loading,
fetchUser,
}
},
}
核心 API 详解
1. 响应式 API
ref:基础响应式引用
import { ref, isRef, unref } from 'vue'
// 创建响应式引用
const count = ref(0)
const message = ref('Hello')
// 访问和修改值
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
// 类型检查和解包
console.log(isRef(count)) // true
console.log(unref(count)) // 1 (等同于 count.value)
// 模板中自动解包
// <template>{{ count }}</template> // 不需要 .value
reactive:深层响应式对象
import { reactive, isReactive, toRefs } from 'vue'
// 创建响应式对象
const state = reactive({
count: 0,
user: {
name: 'John',
age: 30,
},
todos: [],
})
// 直接访问属性
state.count++
state.user.name = 'Jane'
state.todos.push({ id: 1, text: 'Learn Vue 3' })
// 解构响应式对象
const { count, user } = toRefs(state)
// 现在 count 和 user 都是 ref
computed:计算属性
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 只读计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 可写计算属性
const fullNameWritable = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value) {
;[firstName.value, lastName.value] = value.split(' ')
},
})
fullNameWritable.value = 'Jane Smith'
console.log(firstName.value) // 'Jane'
console.log(lastName.value) // 'Smith'
watch 和 watchEffect:侦听器
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 侦听单个源
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 侦听多个源
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log('Multiple values changed')
})
// 立即执行的侦听器
watchEffect(() => {
console.log(`Count is ${count.value}`)
console.log(`Message is ${message.value}`)
})
// 停止侦听
const stopWatcher = watch(count, () => {
// 侦听逻辑
})
// 在某个时机停止侦听
stopWatcher()
2. 生命周期钩子
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('组件即将挂载')
})
onMounted(() => {
console.log('组件已挂载')
})
onBeforeUpdate(() => {
console.log('组件即将更新')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
onUnmounted(() => {
console.log('组件已卸载')
})
onErrorCaptured((error, instance, info) => {
console.log('捕获到错误:', error)
return false // 阻止错误继续传播
})
},
}
组合式函数(Composables)
1. 基础组合式函数
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => (count.value = initialValue)
const isEven = computed(() => count.value % 2 === 0)
const isPositive = computed(() => count.value > 0)
return {
count,
increment,
decrement,
reset,
isEven,
isPositive,
}
}
// 在组件中使用
import { useCounter } from '@/composables/useCounter'
export default {
setup() {
const { count, increment, decrement, isEven } = useCounter(10)
return {
count,
increment,
decrement,
isEven,
}
},
}
2. 异步数据获取
// composables/useFetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(unref(url))
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
if (isRef(url)) {
// 如果 URL 是响应式的,监听其变化
watchEffect(fetchData)
} else {
// 如果 URL 是静态的,直接获取数据
fetchData()
}
return {
data,
error,
loading,
refetch: fetchData,
}
}
// 使用示例
export default {
setup() {
const userId = ref(1)
const userUrl = computed(() => `/api/users/${userId.value}`)
const { data: user, loading, error, refetch } = useFetch(userUrl)
return {
user,
loading,
error,
refetch,
userId,
}
},
}
3. 本地存储组合式函数
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const initialValue = storedValue ? JSON.parse(storedValue) : defaultValue
const value = ref(initialValue)
// 监听值的变化,同步到 localStorage
watch(
value,
(newValue) => {
if (newValue === null || newValue === undefined) {
localStorage.removeItem(key)
} else {
localStorage.setItem(key, JSON.stringify(newValue))
}
},
{ deep: true }
)
// 监听 storage 事件,同步其他标签页的变化
window.addEventListener('storage', (e) => {
if (e.key === key && e.newValue !== null) {
value.value = JSON.parse(e.newValue)
}
})
return value
}
// 使用示例
export default {
setup() {
const theme = useLocalStorage('theme', 'light')
const userPreferences = useLocalStorage('userPreferences', {
language: 'zh-CN',
notifications: true,
})
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
return {
theme,
userPreferences,
toggleTheme,
}
},
}
高级模式
1. 依赖注入
// 父组件
import { provide, ref } from 'vue'
export default {
setup() {
const theme = ref('light')
const user = ref({ name: 'John', role: 'admin' })
// 提供数据
provide('theme', theme)
provide('user', user)
return {
theme,
user
}
}
}
// 子组件
import { inject } from 'vue'
export default {
setup() {
// 注入数据
const theme = inject('theme')
const user = inject('user')
// 带默认值的注入
const config = inject('config', { api: '/api' })
return {
theme,
user,
config
}
}
}
2. 模板引用
import { ref, onMounted } from 'vue'
export default {
setup() {
const inputRef = ref(null)
const listRef = ref([])
onMounted(() => {
// 访问 DOM 元素
inputRef.value.focus()
// 访问组件实例
console.log(listRef.value.length)
})
return {
inputRef,
listRef,
}
},
}
// 模板中使用
// <input ref="inputRef" />
// <li v-for="item in items" :key="item.id" ref="listRef">{{ item.name }}</li>
3. 响应式工具
import { toRef, toRefs, readonly, shallowRef, triggerRef, customRef } from 'vue'
// toRef:为响应式对象的属性创建 ref
const state = reactive({ count: 0, name: 'John' })
const countRef = toRef(state, 'count')
// toRefs:将响应式对象转换为普通对象,每个属性都是 ref
const { count, name } = toRefs(state)
// readonly:创建只读代理
const readonlyState = readonly(state)
// shallowRef:浅层响应式
const shallowState = shallowRef({ nested: { count: 0 } })
shallowState.value = { nested: { count: 1 } } // 触发更新
shallowState.value.nested.count = 2 // 不会触发更新
// 手动触发更新
triggerRef(shallowState)
// 自定义 ref
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
},
}))
}
最佳实践
1. 逻辑复用
// 将相关逻辑组织在一起
function useUserManagement() {
const users = ref([])
const loading = ref(false)
const error = ref(null)
const fetchUsers = async () => {
loading.value = true
try {
users.value = await api.getUsers()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
const addUser = async (userData) => {
const newUser = await api.createUser(userData)
users.value.push(newUser)
}
const removeUser = async (userId) => {
await api.deleteUser(userId)
users.value = users.value.filter((user) => user.id !== userId)
}
return {
users,
loading,
error,
fetchUsers,
addUser,
removeUser,
}
}
2. 类型安全(TypeScript)
import { ref, computed, Ref, ComputedRef } from 'vue'
interface User {
id: number
name: string
email: string
}
interface UseUserReturn {
user: Ref<User | null>
loading: Ref<boolean>
error: Ref<string | null>
fetchUser: (id: number) => Promise<void>
updateUser: (userData: Partial<User>) => Promise<void>
}
export function useUser(): UseUserReturn {
const user = ref<User | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const fetchUser = async (id: number): Promise<void> => {
loading.value = true
error.value = null
try {
const response = await api.getUser(id)
user.value = response.data
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
loading.value = false
}
}
const updateUser = async (userData: Partial<User>): Promise<void> => {
if (!user.value) return
try {
const response = await api.updateUser(user.value.id, userData)
user.value = response.data
} catch (err) {
error.value = err instanceof Error ? err.message : 'Update failed'
}
}
return {
user,
loading,
error,
fetchUser,
updateUser,
}
}
总结
Vue 3 Composition API 为我们提供了更灵活、更强大的组件逻辑组织方式:
- 更好的逻辑复用:通过组合式函数实现逻辑的提取和复用
- 更好的类型推导:与 TypeScript 的完美结合
- 更灵活的代码组织:相关逻辑可以组织在一起
- 更好的性能:更精确的依赖追踪
掌握 Composition API,你就能充分发挥 Vue 3 的威力,构建更加优雅和可维护的应用!
Vue 3 Composition API 是现代 Vue 开发的核心,值得我们深入学习和实践。