
通过实际场景掌握 Vue3 Composition API 的核心用法
Vue3 的 Composition API 改变了我们编写 Vue 组件的方式。相比 Options API,它提供了更好的逻辑复用、类型推导和代码组织能力。本文将通过几个实战场景,帮你快速掌握 Composition API 的精髓。
<!-- Options API -->
<script>
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
}
}
</script>
<!-- Composition API -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>让我们从一个常见的用户认证场景开始:
// composables/useAuth.ts
import { ref, computed } from 'vue'
const currentUser = ref(null)
const loading = ref(false)
export function useAuth() {
const isAuthenticated = computed(() => !!currentUser.value)
const login = async (credentials) => {
loading.value = true
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
if (response.ok) {
currentUser.value = await response.json()
return { success: true }
}
} catch (error) {
return { success: false, error: error.message }
} finally {
loading.value = false
}
}
const logout = () => {
currentUser.value = null
localStorage.removeItem('auth_token')
}
return {
currentUser: readonly(currentUser),
loading: readonly(loading),
isAuthenticated,
login,
logout
}
}在组件中使用:
<template>
<div>
<div v-if="isAuthenticated">
<p>欢迎,{{ currentUser.name }}!</p>
<button @click="logout">退出登录</button>
</div>
<div v-else>
<button @click="handleLogin" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
</div>
</div>
</template>
<script setup>
import { useAuth } from '@/composables/useAuth'
const { currentUser, loading, isAuthenticated, login, logout } = useAuth()
const handleLogin = async () => {
const result = await login({
email: 'user@example.com',
password: 'password'
})
if (!result.success) {
alert('登录失败:' + result.error)
}
}
</script>创建一个通用的数据获取 Hook:
// composables/useFetch.ts
import { ref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(url.value || url)
if (!response.ok) throw new Error('请求失败')
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 自动监听 URL 变化
watchEffect(() => {
if (url.value || url) {
fetchData()
}
})
return { data, loading, error, refresh: fetchData }
}使用示例:
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误:{{ error }}</div>
<div v-else>
<h2>用户列表</h2>
<ul>
<li v-for="user in data" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
<button @click="refresh">刷新</button>
</div>
</div>
</template>
<script setup>
import { useFetch } from '@/composables/useFetch'
const { data, loading, error, refresh } = useFetch('/api/users')
</script>创建一个简单而强大的表单验证 Hook:
// composables/useForm.ts
import { reactive, computed } from 'vue'
export function useForm(initialData, rules = {}) {
const formData = reactive({ ...initialData })
const errors = reactive({})
const validateField = (field) => {
const value = formData[field]
const fieldRules = rules[field] || []
for (const rule of fieldRules) {
if (rule.required && !value) {
errors[field] = '此字段为必填项'
return false
}
if (rule.minLength && value.length < rule.minLength) {
errors[field] = `最少需要 ${rule.minLength} 个字符`
return false
}
if (rule.pattern && !rule.pattern.test(value)) {
errors[field] = '格式不正确'
return false
}
}
delete errors[field]
return true
}
const validateForm = () => {
let isValid = true
Object.keys(rules).forEach(field => {
if (!validateField(field)) {
isValid = false
}
})
return isValid
}
const isValid = computed(() => Object.keys(errors).length === 0)
return {
formData,
errors,
isValid,
validateField,
validateForm
}
}在登录表单中使用:
<template>
<form @submit.prevent="handleSubmit">
<div>
<input
v-model="formData.email"
@blur="validateField('email')"
placeholder="邮箱"
:class="{ error: errors.email }"
/>
<span v-if="errors.email" class="error-text">{{ errors.email }}</span>
</div>
<div>
<input
v-model="formData.password"
@blur="validateField('password')"
type="password"
placeholder="密码"
:class="{ error: errors.password }"
/>
<span v-if="errors.password" class="error-text">{{ errors.password }}</span>
</div>
<button type="submit" :disabled="!isValid">登录</button>
</form>
</template>
<script setup>
import { useForm } from '@/composables/useForm'
import { useAuth } from '@/composables/useAuth'
const { login } = useAuth()
const { formData, errors, isValid, validateField, validateForm } = useForm(
{ email: '', password: '' },
{
email: [
{ required: true },
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }
],
password: [
{ required: true },
{ minLength: 6 }
]
}
)
const handleSubmit = async () => {
if (!validateForm()) return
const result = await login(formData)
if (!result.success) {
alert('登录失败')
}
}
</script>实现一个完整的主题切换功能:
// composables/useTheme.ts
import { ref, watch, onMounted } from 'vue'
const currentTheme = ref('light')
export function useTheme() {
const isDark = computed(() => currentTheme.value === 'dark')
const setTheme = (theme) => {
currentTheme.value = theme
document.documentElement.classList.toggle('dark', theme === 'dark')
localStorage.setItem('theme', theme)
}
const toggleTheme = () => {
setTheme(currentTheme.value === 'light' ? 'dark' : 'light')
}
onMounted(() => {
const savedTheme = localStorage.getItem('theme') || 'light'
setTheme(savedTheme)
})
return {
currentTheme: readonly(currentTheme),
isDark,
setTheme,
toggleTheme
}
}使用主题切换:
<template>
<button @click="toggleTheme" class="theme-toggle">
{{ isDark ? '🌙' : '☀️' }}
{{ isDark ? '深色模式' : '浅色模式' }}
</button>
</template>
<script setup>
import { useTheme } from '@/composables/useTheme'
const { isDark, toggleTheme } = useTheme()
</script>
<style>
.theme-toggle {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: var(--bg-color);
color: var(--text-color);
cursor: pointer;
}
:root {
--bg-color: #ffffff;
--text-color: #333333;
}
:root.dark {
--bg-color: #1a1a1a;
--text-color: #ffffff;
}
</style>// ✅ 使用 use 前缀
export function useAuth() {}
export function useTheme() {}
// ❌ 避免
export function auth() {}
export function theme() {}export function useCounter() {
const count = ref(0)
return {
count: readonly(count), // 防止外部直接修改
increment: () => count.value++
}
}// ✅ 基本类型用 ref
const count = ref(0)
const message = ref('')
// ✅ 对象用 reactive
const user = reactive({
name: '',
email: ''
})Composition API 为 Vue3 带来了更强大的逻辑组织和复用能力。通过本文的实战场景,你应该能够:
记住,选择合适的 API 比使用最新的 API 更重要。根据项目需求和团队情况,做出明智的技术选择。
开始你的 Vue3 Composition API 之旅吧!如果你有任何问题,欢迎在评论区讨论。