组合式API #
一、组合式API概述 #
1.1 什么是组合式API #
组合式API(Composition API)是Vue 3引入的一组新API,允许使用函数组织组件逻辑。
1.2 与选项式API对比 #
vue
<!-- 选项式API -->
<script>
export default {
data() {
return {
count: 0,
name: '张三'
}
},
computed: {
doubled() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('组件已挂载')
}
}
</script>
<!-- 组合式API -->
<script setup>
import { ref, computed, onMounted } from 'vue'
const count = ref(0)
const name = ref('张三')
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件已挂载')
})
</script>
1.3 组合式API的优势 #
- 更好的逻辑复用:通过组合式函数实现
- 更灵活的代码组织:按功能而非选项分组
- 更好的类型推断:TypeScript支持更完善
- 更小的打包体积:Tree-shaking更有效
二、setup函数 #
2.1 基本使用 #
vue
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
}
}
</script>
2.2 setup语法糖 #
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
2.3 访问Props #
vue
<script setup>
const props = defineProps({
title: String,
count: {
type: Number,
default: 0
}
})
console.log(props.title)
</script>
2.4 定义Emits #
vue
<script setup>
const emit = defineEmits(['update', 'delete'])
function handleClick() {
emit('update', { id: 1 })
}
</script>
2.5 setup参数 #
vue
<script>
export default {
setup(props, context) {
// props - 响应式的props对象
console.log(props.title)
// context - 上下文对象
// context.attrs - 非props属性
// context.slots - 插槽
// context.emit - 触发事件
// context.expose - 暴露给模板引用
return {}
}
}
</script>
三、生命周期钩子 #
3.1 钩子函数对照表 #
| 选项式API | 组合式API |
|---|---|
| beforeCreate | setup() |
| created | setup() |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
3.2 使用示例 #
vue
<script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
onBeforeMount(() => {
console.log('组件挂载前')
})
onMounted(() => {
console.log('组件已挂载')
// DOM操作、事件监听、定时器等
})
onBeforeUpdate(() => {
console.log('组件更新前')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件卸载前')
// 清理工作
})
onUnmounted(() => {
console.log('组件已卸载')
// 清理定时器、事件监听等
})
</script>
3.3 清理副作用 #
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const timer = ref(null)
onMounted(() => {
timer.value = setInterval(() => {
console.log('定时器执行')
}, 1000)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 清理定时器
clearInterval(timer.value)
// 移除事件监听
window.removeEventListener('resize', handleResize)
})
function handleResize() {
console.log('窗口大小变化')
}
</script>
3.4 使用watchEffect自动清理 #
vue
<script setup>
import { ref, watchEffect } from 'vue'
const userId = ref(1)
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch(`/api/users/${userId.value}`, {
signal: controller.signal
})
.then(res => res.json())
.then(data => {
// 处理数据
})
// 自动清理:下次执行前或组件卸载时调用
onCleanup(() => {
controller.abort()
})
})
</script>
四、依赖注入 #
4.1 provide和inject #
vue
<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
const user = ref({ name: '张三' })
provide('theme', theme)
provide('user', user)
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const user = inject('user')
console.log(theme.value) // 'dark'
</script>
4.2 提供默认值 #
vue
<script setup>
import { inject } from 'vue'
// 提供默认值
const theme = inject('theme', 'light')
// 使用工厂函数
const config = inject('config', () => ({
apiUrl: '/api',
timeout: 5000
}))
</script>
4.3 响应式注入 #
vue
<!-- 祖先组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'
const state = ref({
count: 0,
user: null
})
// 提供只读状态
provide('state', readonly(state))
// 提供修改方法
provide('actions', {
increment: () => state.value.count++,
setUser: (user) => state.value.user = user
})
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'
const state = inject('state')
const actions = inject('actions')
function handleClick() {
actions.increment()
}
</script>
4.4 使用Symbol作为key #
javascript
// keys.js
export const ThemeKey = Symbol('theme')
export const UserKey = Symbol('user')
export const StateKey = Symbol('state')
vue
<script setup>
import { provide, inject } from 'vue'
import { ThemeKey, UserKey } from './keys'
// 提供
provide(ThemeKey, 'dark')
// 注入
const theme = inject(ThemeKey)
</script>
五、模板引用 #
5.1 基本使用 #
vue
<template>
<input ref="inputRef">
<button @click="focusInput">聚焦</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
function focusInput() {
inputRef.value.focus()
}
onMounted(() => {
inputRef.value.focus()
})
</script>
5.2 组件引用 #
vue
<template>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function callChildMethod() {
childRef.value.someMethod()
}
</script>
vue
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
function someMethod() {
console.log('子组件方法')
}
// 暴露给父组件
defineExpose({
count,
increment,
someMethod
})
</script>
5.3 v-for中的引用 #
vue
<template>
<div v-for="item in items" :key="item.id" :ref="setItemRef">
{{ item.name }}
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const items = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
])
const itemRefs = ref([])
function setItemRef(el) {
if (el) {
itemRefs.value.push(el)
}
}
onMounted(() => {
console.log(itemRefs.value) // 所有DOM元素
})
</script>
六、组合式函数 #
6.1 创建组合式函数 #
javascript
// useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
doubled,
increment,
decrement,
reset
}
}
6.2 使用组合式函数 #
vue
<template>
<div>
<p>计数: {{ count }}</p>
<p>双倍: {{ doubled }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './useCounter'
const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>
6.3 带参数的组合式函数 #
javascript
// useFetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
async function fetchData() {
loading.value = true
error.value = null
try {
const response = await fetch(toValue(url))
data.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchData()
})
return {
data,
error,
loading,
refetch: fetchData
}
}
vue
<script setup>
import { ref } from 'vue'
import { useFetch } from './useFetch'
const userId = ref(1)
const { data, error, loading, refetch } = useFetch(
() => `/api/users/${userId.value}`
)
</script>
6.4 返回响应式对象 #
javascript
// useUser.js
import { reactive, toRefs } from 'vue'
export function useUser() {
const state = reactive({
user: null,
loading: false,
error: null
})
async function fetchUser(id) {
state.loading = true
state.error = null
try {
const response = await fetch(`/api/users/${id}`)
state.user = await response.json()
} catch (e) {
state.error = e
} finally {
state.loading = false
}
}
return {
...toRefs(state),
fetchUser
}
}
vue
<script setup>
import { useUser } from './useUser'
const { user, loading, error, fetchUser } = useUser()
fetchUser(1)
</script>
七、最佳实践 #
7.1 命名规范 #
javascript
// 组合式函数以use开头
export function useCounter() {}
export function useFetch() {}
export function useLocalStorage() {}
// 响应式变量使用描述性名称
const isLoading = ref(false)
const hasError = ref(false)
const userList = ref([])
7.2 参数处理 #
javascript
import { toValue, toRef, unref } from 'vue'
export function useExample(source) {
// toValue - 解包ref和getter
const value = toValue(source)
// 支持ref、reactive、getter作为参数
const data = computed(() => {
return fetchData(toValue(source))
})
return { data }
}
// 使用方式灵活
useFetch('/api/data') // 字符串
useFetch(ref('/api/data')) // ref
useFetch(() => `/api/${id}`) // getter
7.3 返回值规范 #
javascript
// 返回ref或reactive对象
export function useCounter() {
const count = ref(0)
return { count } // 返回ref
}
// 使用toRefs保持响应性
export function useUser() {
const state = reactive({
user: null,
loading: false
})
return toRefs(state) // 解构后仍保持响应性
}
// 返回只读对象
export function useStore() {
const state = reactive({ /* ... */ })
return {
state: readonly(state),
actions: { /* ... */ }
}
}
八、总结 #
组合式API核心 #
| API | 用途 |
|---|---|
setup |
组合式API入口 |
ref |
创建响应式引用 |
reactive |
创建响应式对象 |
computed |
创建计算属性 |
watch |
侦听数据变化 |
watchEffect |
自动追踪依赖 |
provide/inject |
依赖注入 |
onMounted等 |
生命周期钩子 |
最佳实践:
- 优先使用
<script setup>语法 - 组合式函数以
use开头命名 - 使用
toRefs返回响应式对象 - 合理使用
provide/inject - 在
onUnmounted中清理副作用
最后更新:2026-03-26