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
# 使用 Vite 创建 Vue 3 项目
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
// src/router/index.js
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
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

五、状态管理(Pinia)

1
npm install 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
// src/stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
// state
const token = ref(null)
const userInfo = ref(null)

// getter
const isLoggedIn = computed(() => !!token.value)
const username = computed(() => userInfo.value?.name ?? '未登录')

// action
async function login(username, password) {
// 调用 API
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
// src/composables/useFetch.js
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
// src/utils/request.js — Axios 封装
import axios from 'axios'
import { useUserStore } from '@/stores/user'

const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000
})

// 请求拦截器 - 自动带 token
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) {
// token 过期,跳转登录
}
return Promise.reject(error)
}
)

export default request

八、总结

Vue 3 + Composition API 的核心优势:

特性 优势
ref / reactive 灵活的响应式数据
computed 自动缓存的计算属性
watch 细粒度的变化侦听
Composables 逻辑复用,替代 mixin
Pinia 类型安全的状态管理
Vite 极速开发体验

首发于 CaoZH 的笔记