Vue 3 从基础到高阶全攻略
探索 Vue 3 的无限可能 🚀
引言
Vue 3 作为当前最流行的前端框架之一,带来了许多令人振奋的新特性和性能改进。从组合式 API 到更好的 TypeScript 支持,从更小的打包体积到更快的渲染速度,Vue 3 为前端开发者提供了更现代、更高效的开发体验。本文将带你从基础到高阶,全面掌握 Vue 3 的核心技术!
一、快速上手
环境搭建
使用 Vite 创建项目
bash
# npm
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
安装依赖并启动
bash
cd my-vue-app
npm install
npm run dev
第一个 Vue 3 应用
vue
<!-- App.vue -->
<template>
<div>
<h1>{{ message }}</h1>
<button @click="count++">点击次数: {{ count }}</button>
</div>
</template>
<script setup>
// 响应式数据
import { ref } from 'vue'
const message = 'Hello Vue 3!'
const count = ref(0)
</script>
<style scoped>
button {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #35495e;
}
</style>
二、核心特性
1. 组合式 API
组合式 API 是 Vue 3 最重要的新特性之一,它允许我们根据逻辑相关性组织代码,而不是根据选项类型。
setup 语法糖
vue
<template>
<div>
<p>姓名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
<button @click="updateUser">更新用户信息</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// 响应式对象
const user = reactive({
name: '张三',
age: 30
})
// 方法
function updateUser() {
user.name = '李四'
user.age = 25
}
</script>
ref vs reactive
vue
<template>
<div>
<h2>ref 示例</h2>
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
<h2>reactive 示例</h2>
<p>用户: {{ user.name }}, 年龄: {{ user.age }}</p>
<button @click="updateUser">更新</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// 基本类型响应式
const count = ref(0)
function increment() {
count.value++ // ref 需要通过 .value 访问
}
// 对象类型响应式
const user = reactive({
name: 'Vue',
age: 3
})
function updateUser() {
user.name = 'Vue 3' // reactive 直接修改属性
user.age = 4
}
</script>
2. 计算属性与监听
computed
vue
<template>
<div>
<p>原始价格: {{ price }}</p>
<p>折扣价格: {{ discountedPrice }}</p>
<input v-model="discount" type="number" placeholder="折扣率(%)">
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(100)
const discount = ref(10)
// 计算属性 - 依赖变化时自动重新计算
const discountedPrice = computed(() => {
return price.value * (1 - discount.value / 100)
})
</script>
watch
vue
<template>
<div>
<input v-model="query" placeholder="搜索内容">
<p>搜索结果: {{ results.length }} 条</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const query = ref('')
const results = ref([])
// 监听 query 变化
watch(query, (newQuery, oldQuery) => {
console.log(`搜索从 "${oldQuery}" 变为 "${newQuery}"`)
if (newQuery) {
fetchResults(newQuery)
} else {
results.value = []
}
})
async function fetchResults(query) {
// 模拟 API 请求
await new Promise(resolve => setTimeout(resolve, 500))
results.value = [
{ id: 1, title: `结果 1: ${query}` },
{ id: 2, title: `结果 2: ${query}` }
]
}
</script>
3. 生命周期钩子
vue
<template>
<div>
<p>生命周期演示</p>
<button @click="destroy">销毁组件</button>
</div>
</template>
<script setup>
import { onMounted, onUpdated, onUnmounted, ref } from 'vue'
const count = ref(0)
// 组件挂载后
onMounted(() => {
console.log('组件已挂载')
// 可以在这里初始化数据、绑定事件等
})
// 组件更新后
onUpdated(() => {
console.log('组件已更新')
// 可以在这里处理 DOM 更新后的逻辑
})
// 组件卸载前
onUnmounted(() => {
console.log('组件将卸载')
// 可以在这里清理定时器、事件监听器等
})
function destroy() {
// 销毁组件的逻辑
}
</script>
4. 组件通信
Props
vue
<!-- ParentComponent.vue -->
<template>
<div>
<ChildComponent :message="parentMessage" :count="parentCount" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const parentMessage = ref('Hello from parent')
const parentCount = ref(10)
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
<p>{{ count }}</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
// 定义接收的 props
const props = defineProps({
message: String,
count: Number
})
</script>
Emits
vue
<!-- ChildComponent.vue -->
<template>
<div>
<button @click="handleClick">点击我</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue'
// 定义可以触发的事件
const emit = defineEmits(['click', 'customEvent'])
function handleClick() {
// 触发事件并传递数据
emit('click', '按钮被点击了')
emit('customEvent', { id: 1, timestamp: Date.now() })
}
</script>
<!-- ParentComponent.vue -->
<template>
<div>
<ChildComponent @click="onChildClick" @customEvent="onCustomEvent" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
function onChildClick(message) {
console.log('子组件事件:', message)
}
function onCustomEvent(data) {
console.log('自定义事件数据:', data)
}
</script>
三、高级特性
1. 自定义 Hook
javascript
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0, step = 1) {
const count = ref(initialValue)
// 计算属性
const isEven = computed(() => count.value % 2 === 0)
const isOdd = computed(() => count.value % 2 !== 0)
// 方法
function increment() {
count.value += step
}
function decrement() {
count.value -= step
}
function reset() {
count.value = initialValue
}
return {
count,
isEven,
isOdd,
increment,
decrement,
reset
}
}
使用自定义 Hook:
vue
<template>
<div>
<h2>计数器: {{ count }}</h2>
<p>是否为偶数: {{ isEven ? '是' : '否' }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { useCounter } from './composables/useCounter'
// 使用自定义 Hook
const { count, isEven, increment, decrement, reset } = useCounter(10, 2)
</script>
2. 组件插槽
基础插槽
vue
<!-- Button.vue -->
<template>
<button class="custom-button">
<!-- 插槽位置 -->
<slot></slot>
</button>
</template>
<style scoped>
.custom-button {
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
<!-- 使用 Button 组件 -->
<template>
<Button>
<span>点击我</span> <!-- 插槽内容 -->
</Button>
</template>
<script setup>
import Button from './Button.vue'
</script>
具名插槽
vue
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header"></slot>
</div>
<div class="card-body">
<slot></slot> <!-- 默认插槽 -->
</div>
<div class="card-footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<style scoped>
.card {
border: 1px solid #ccc;
border-radius: 4px;
padding: 16px;
margin: 16px;
}
.card-header {
font-weight: bold;
margin-bottom: 8px;
}
.card-footer {
margin-top: 8px;
text-align: right;
}
</style>
<!-- 使用 Card 组件 -->
<template>
<Card>
<template #header>
<h3>卡片标题</h3>
</template>
<p>这是卡片的主要内容...</p>
<p>可以包含多行文本</p>
<template #footer>
<button>确定</button>
<button>取消</button>
</template>
</Card>
</template>
<script setup>
import Card from './Card.vue'
</script>
3. Teleport
Teleport 允许我们将组件的一部分渲染到 DOM 树中的另一个位置,非常适合模态框、通知等场景。
vue
<template>
<div>
<button @click="showModal = true">打开模态框</button>
<Teleport to="body">
<div v-if="showModal" class="modal-overlay" @click="showModal = false">
<div class="modal-content" @click.stop>
<h2>模态框标题</h2>
<p>这是模态框内容</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 4px;
width: 80%;
max-width: 500px;
}
</style>
四、性能优化
1. 编译时优化
Vue 3 在编译阶段进行了多项优化:
- Tree-shaking 支持:只打包实际使用的功能
- Patch Flag:标记模板中变化的部分,减少 DOM 比对
- 静态提升:将静态内容提升到渲染函数外部,避免重复创建
2. 运行时优化
v-memo
vue
<template>
<div>
<div v-for="item in items" :key="item.id" v-memo="[item.id, item.visible]">
<p>{{ item.name }}</p>
<p>{{ item.description }}</p>
<!-- 只有当 item.id 或 item.visible 变化时,才会重新渲染 -->
</div>
</div>
</template>
v-once
vue
<template>
<div>
<h1 v-once>{{ staticTitle }}</h1>
<!-- 这个标题只会渲染一次,之后不会再更新 -->
<p>{{ dynamicContent }}</p>
<!-- 这个内容会正常更新 -->
</div>
</template>
3. 按需导入
javascript
// main.js
import { createApp } from 'vue'
// 按需导入组件
import { Button, Input } from 'element-plus'
import App from './App.vue'
const app = createApp(App)
// 注册需要的组件
app.use(Button)
app.use(Input)
app.mount('#app')
五、状态管理
Pinia 基础
Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者。
安装
bash
npm install pinia
配置
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
创建 Store
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 状态
state: () => ({
count: 0,
name: 'Vue 3'
}),
// 计算属性
getters: {
doubleCount: (state) => state.count * 2,
isEven: (state) => state.count % 2 === 0
},
// 方法
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
reset() {
this.count = 0
}
}
})
使用 Store
vue
<template>
<div>
<h2>计数: {{ counterStore.count }}</h2>
<h3>双倍计数: {{ counterStore.doubleCount }}</h3>
<p>是否为偶数: {{ counterStore.isEven ? '是' : '否' }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
<button @click="counterStore.reset">重置</button>
</div>
</template>
<script setup>
import { useCounterStore } from './stores/counter'
// 使用 store
const counterStore = useCounterStore()
</script>
六、路由
Vue Router 4 基础
安装
bash
npm install vue-router@4
配置
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
使用路由
vue
<!-- App.vue -->
<template>
<div>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>
</nav>
<main>
<router-view></router-view>
</main>
</div>
</template>
<style>
nav {
padding: 20px;
background-color: #f5f5f5;
}
router-link {
margin-right: 10px;
text-decoration: none;
color: #42b983;
}
router-link.router-link-active {
font-weight: bold;
}
</style>
七、最佳实践
1. 代码组织
- 使用
composables目录存放可复用的自定义 Hook - 按功能模块组织组件
- 合理使用
utils目录存放工具函数
2. TypeScript 支持
Vue 3 对 TypeScript 有很好的支持:
vue
<template>
<div>
<p>{{ user.name }}</p>
<p>{{ user.age }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
interface User {
name: string
age: number
email?: string
}
const user = ref<User>({
name: '张三',
age: 30
})
</script>
3. 测试策略
- 使用 Vitest 进行单元测试
- 使用 Cypress 进行端到端测试
- 编写组件测试验证组件行为
八、总结
Vue 3 为前端开发带来了许多强大的新特性和性能改进,从组合式 API 到更好的 TypeScript 支持,从更小的打包体积到更快的渲染速度。通过本文的学习,你应该已经掌握了 Vue 3 的核心概念和高级技巧,可以开始构建现代化的 Vue 3 应用了!
记住,实践是最好的学习方式。不断尝试新的特性,探索不同的应用场景,你会发现 Vue 3 的无限可能!
✨ Happy Vue 3 Coding! ✨