跳到主要内容

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 的高级内容:

  1. setup() 和 script setup:组合式 API 的入口
  2. Composables:逻辑复用的最佳实践
  3. provide/inject:跨级组件通信
  4. 模板引用:访问 DOM 和子组件
  5. 异步组件:按需加载组件
  6. 高级模式:代码组织和复用技巧

练习

  1. 创建一个 useDebounce Composable
  2. 创建一个 usePagination Composable
  3. 创建一个表单验证 Composable

准备好进入下一章,学习速查表了吗?