Vue 组合式 API
组合式 API(Composition API)是 Vue 3 引入的新特性,它提供了一种更灵活的方式来组织组件逻辑。本章将详细介绍组合式 API 的高级用法和最佳实践。
setup() 函数
setup() 是组合式 API 的入口点,在组件实例创建之前执行:
<script>
export default {
setup() {
// 组件逻辑
return {
// 需要暴露给模板的属性
}
}
}
</script>
script setup 语法糖
<script setup> 是 setup() 的语法糖,更加简洁:
<script setup>
// 所有代码都在 setup 中执行
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
// 不需要 return,模板可以直接访问
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
逻辑复用:Composables
Composables(组合式函数)是 Vue 3 推荐的逻辑复用方式,类似于 React 的 Hooks。
什么是 Composable?
Composable 是一个函数,使用 Vue 的响应式 API,可以复用有状态逻辑:
// useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}
使用 Composable
<script setup>
import { useCounter } from './composables/useCounter'
// 组件中使用
const { count, increment, decrement, reset } = useCounter(10)
</script>
<template>
<div>
<p>计数: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">重置</button>
</div>
</template>
示例:useMousePosition
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function handleMouseMove(event) {
x.value = event.clientX
y.value = event.clientY
}
onMounted(() => {
window.addEventListener('mousemove', handleMouseMove)
})
onUnmounted(() => {
window.removeEventListener('mousemove', handleMouseMove)
})
return { x, y }
}
<script setup>
import { useMousePosition } from './useMousePosition'
const { x, y } = useMousePosition()
</script>
<template>
<p>鼠标位置: {{ x }}, {{ y }}</p>
</template>
示例:useLocalStorage
// useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key)
const data = ref(stored ? JSON.parse(stored) : defaultValue)
watch(data, (newValue) => {
localStorage.setItem(key, JSON.stringify(newValue))
}, { deep: true })
return data
}
<script setup>
import { useLocalStorage } from './useLocalStorage'
// 跨组件共享数据
const username = useLocalStorage('username', '')
const theme = useLocalStorage('theme', 'light')
</script>
示例:useFetch
// useFetch.js
import { ref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(true)
async function fetchData() {
loading.value = true
error.value = null
try {
const response = await fetch(url.value)
if (!response.ok) {
throw new Error('请求失败')
}
data.value = await response.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
// 当 URL 变化时重新获取
watchEffect(() => {
if (url.value) {
fetchData()
}
})
return {
data,
error,
loading,
refetch: fetchData
}
}
异步组件
defineAsyncComponent
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => {
return new Promise((resolve) => {
// 模拟异步加载
setTimeout(() => {
resolve({
template: '<div>异步加载的组件</div>'
})
}, 1000)
})
})
// 或使用 import
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
Suspense 与异步组件
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncUsers = defineAsyncComponent(() =>
import('./components/Users.vue')
)
</script>
<template>
<Suspense>
<template #default>
<AsyncUsers />
</template>
<template #fallback>
<p>加载中...</p>
</template>
</Suspense>
</template>
依赖注入
provide / inject
// 父组件
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
// 或提供静态值
provide('appName', 'My App')
// 后代组件
import { inject } from 'vue'
const theme = inject('theme')
const appName = inject('appName', 'Default App') // 带默认值
响应式注入
// 父组件
import { provide, reactive } from 'vue'
provide('user', reactive({
name: '张三',
age: 25
}))
// 后代组件 - 自动保持响应式
import { inject } from 'vue'
const user = inject('user')
user.age = 30 // 父组件也能感知到变化
模板引用
ref 属性
<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
const countRef = ref(null)
function focusInput() {
inputRef.value?.focus()
}
function getCount() {
console.log(countRef.value?.innerText)
}
</script>
<template>
<input ref="inputRef" type="text" />
<button @click="focusInput">聚焦</button>
<p ref="countRef">计数: 0</p>
<button @click="getCount">获取计数</button>
</template>
函数类型 ref
<script setup>
import { ref } from 'vue'
// 动态绑定元素
const elementRef = ref()
function setRef(el) {
elementRef.value = el
el?.focus()
}
</script>
<template>
<input :ref="setRef" />
</template>
组件引用
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
defineExpose({ count, increment }) // 暴露给父组件
</script>
<template>
<p>{{ count }}</p>
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
function handleClick() {
childRef.value?.increment()
}
</script>
<template>
<Child ref="childRef" />
<button @click="handleClick">子组件 +1</button>
</template>
高级模式
模式:在 setup 中监听
<script setup>
import { ref, watch, onMounted } from 'vue'
const count = ref(0)
watch(count, (newValue) => {
console.log('count 变化:', newValue)
})
onMounted(() => {
console.log('mounted')
})
// 逻辑可以按功能分组
const userData = ref({})
watch(userData, (newValue) => {
console.log('用户数据变化')
})
// 而不是按生命周期钩子分组
</script>
模式:逻辑复用
// 分离关注点
// mouse.js
export function useMouse() { /* 鼠标逻辑 */ }
// keyboard.js
export function useKeyboard() { /* 键盘逻辑 */ }
// event.js
export function useEventListener(target, event, callback) {
// 事件监听逻辑
}
<script setup>
import { useMouse } from './composables/mouse'
import { useKeyboard } from './composables/keyboard'
const { x, y } = useMouse()
const { pressedKeys } = useKeyboard()
</script>
小结
本章我们详细学习了组合式 API 的高级内容:
- setup() 和 script setup:组合式 API 的入口
- Composables:逻辑复用的最佳实践
- provide/inject:跨级组件通信
- 模板引用:访问 DOM 和子组件
- 异步组件:按需加载组件
- 高级模式:代码组织和复用技巧
练习
- 创建一个
useDebounceComposable - 创建一个
usePaginationComposable - 创建一个表单验证 Composable
准备好进入下一章,学习速查表了吗?