Vue 3 入门教程——组合式 API 与项目实战
Vue 3 入门教程——组合式 API 与项目实战
作者: CaoZH
日期: 2024-02-15
本文为原创教程
到 2024 年,Vue 3 已经成为前端开发的主流选择。Vite 取代 Webpack 成为默认构建工具,组合式 API(Composition API)取代选项式 API 成为推荐写法。本文从零开始,带你掌握 Vue 3 的核心概念。
一、创建项目
1 2 3 4 5 6 7 8
| npm create vite@latest my-app -- --template vue cd my-app npm install npm run dev
npm create vue@latest my-app
|
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| my-app/ ├── index.html ├── vite.config.js ├── src/ │ ├── main.js # 入口文件 │ ├── App.vue # 根组件 │ ├── components/ # 公共组件 │ ├── views/ # 页面组件 │ ├── router/ # 路由 │ ├── stores/ # 状态管理(Pinia) │ ├── api/ # API 接口 │ └── assets/ # 静态资源 └── package.json
|
二、组合式 API 基础
ref 和 reactive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <script setup> import { ref, reactive } from 'vue'
// ref:基本类型响应式 const count = ref(0) const message = ref('Hello Vue 3!')
// reactive:对象响应式 const user = reactive({ name: '张三', age: 28, hobbies: ['coding', 'reading'] })
// 修改数据 function increment() { count.value++ // ref 需要 .value user.age = 29 // reactive 直接修改 } </script>
<template> <div> <p>{{ message }}</p> <p>计数: {{ count }}</p> <p>用户: {{ user.name }} - {{ user.age }}岁</p> <button @click="increment">+1</button> </div> </template>
|
computed 和 watch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <script setup> import { ref, computed, watch } from 'vue'
const price = ref(100) const quantity = ref(2)
// 计算属性 const total = computed(() => price.value * quantity.value)
// 侦听器 watch(price, (newVal, oldVal) => { console.log(`价格从 ${oldVal} 变为 ${newVal}`) })
// 深度侦听 watch(user, (newVal) => { console.log('用户信息变化了', newVal) }, { deep: true }) </script>
|
三、组件通信
父传子 props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <!-- Child.vue --> <script setup> // 方式一:简单声明 const props = defineProps(['title', 'content'])
// 方式二:带类型和验证 const props = defineProps({ title: { type: String, required: true }, count: { type: Number, default: 0 }, items: { type: Array, default: () => [] } }) </script>
<template> <div class="card"> <h3>{{ title }}</h3> <p>{{ content }}</p> </div> </template>
|
子传父 emit
1 2 3 4 5 6 7 8 9 10 11 12
| <!-- Child.vue --> <script setup> const emit = defineEmits(['update', 'delete'])
function handleUpdate() { emit('update', { id: 1, name: 'new name' }) }
function handleDelete(id) { emit('delete', id) } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- Parent.vue --> <script setup> import Child from './Child.vue'
function onUpdate(data) { console.log('收到更新:', data) }
function onDelete(id) { console.log('删除:', id) } </script>
<template> <Child @update="onUpdate" @delete="onDelete" /> </template>
|
四、路由管理(Vue Router)
1
| npm install vue-router@4
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { createRouter, createWebHistory } from 'vue-router'
const routes = [ { path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { path: '/users', name: 'Users', component: () => import('../views/UserList.vue') }, { path: '/users/:id', name: 'UserDetail', component: () => import('../views/UserDetail.vue') } ]
const router = createRouter({ history: createWebHistory(), routes })
export default router
|
1 2 3 4 5 6
| import { createApp } from 'vue' import App from './App.vue' import router from './router'
createApp(App).use(router).mount('#app')
|
五、状态管理(Pinia)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { defineStore } from 'pinia' import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => { const token = ref(null) const userInfo = ref(null)
const isLoggedIn = computed(() => !!token.value) const username = computed(() => userInfo.value?.name ?? '未登录')
async function login(username, password) { const res = await api.login(username, password) token.value = res.token userInfo.value = res.user }
function logout() { token.value = null userInfo.value = null }
return { token, userInfo, isLoggedIn, username, login, logout } })
|
在组件中使用:
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 直接使用 state console.log(userStore.isLoggedIn)
// 调用 action userStore.login('admin', '123456') </script>
|
六、组合式函数(Composables)
组合式函数是 Vue 3 最强大的复用机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { ref, onMounted } from 'vue'
export function useFetch(url) { const data = ref(null) const loading = ref(true) const error = ref(null)
async function fetchData() { loading.value = true try { const res = await fetch(url) data.value = await res.json() } catch (e) { error.value = e.message } finally { loading.value = false } }
onMounted(fetchData)
return { data, loading, error, refresh: fetchData } }
|
在组件中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script setup> import { useFetch } from '@/composables/useFetch'
const { data: users, loading, error } = useFetch('/api/users') </script>
<template> <div v-if="loading">加载中...</div> <div v-else-if="error">出错了: {{ error }}</div> <ul v-else> <li v-for="user in users" :key="user.id">{{ user.name }}</li> </ul> </template>
|
七、常用工具函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import axios from 'axios' import { useUserStore } from '@/stores/user'
const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 10000 })
request.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config })
request.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { } return Promise.reject(error) } )
export default request
|
八、总结
Vue 3 + Composition API 的核心优势:
| 特性 |
优势 |
✅ ref / reactive |
灵活的响应式数据 |
✅ computed |
自动缓存的计算属性 |
✅ watch |
细粒度的变化侦听 |
✅ Composables |
逻辑复用,替代 mixin |
✅ Pinia |
类型安全的状态管理 |
✅ Vite |
极速开发体验 |
首发于 CaoZH 的笔记