
目录
Vue3 基础语法
Vue3 的模板语法是基于 HTML 的声明式语法,它允许我们在 HTML 中嵌入 Vue 的特殊标记,将响应式数据与 DOM 绑定。

1.1 插值表达式
文本插值:最基础的数据绑定方式
<template> <div> <!-- 文本插值 --> <h1>{{ message }}</h1> <!-- 表达式支持 --> <p>{{ count + 1 }}</p> <p>{{ isActive ? 'Active' : 'Inactive' }}</p> <!-- 原始HTML --> <div v-html="rawHtml"></div> </div></template><script setup>import { ref } from 'vue'const message = ref('Hello Vue3!')const count = ref(0)const isActive = ref(true)const rawHtml = ref('<strong>这是加粗的文本</strong>')</script>
1.2 指令系统
Vue 提供了丰富的指令来处理各种 DOM 操作:
v-bind:属性绑定
<template> <!-- 完整语法 --> <img v-bind:src="imageUrl" v-bind:alt="imageAlt"> <!-- 缩写语法 --> <img :src="imageUrl" :alt="imageAlt"> <!-- 动态属性名 --> <div :[dynamicAttr]="value"></div></template>
v-if/v-else:条件渲染
<template> <div v-if="isLoggedIn"> <p>欢迎回来,{{ username }}</p> </div> <div v-else> <p>请登录</p> </div></template>
v-for:列表渲染
<template> <ul> <li v-for="(item, index) in items" :key="item.id"> {{ index }} - {{ item.name }} </li> </ul></template><script setup>import { ref } from 'vue'const items = ref([ { id: 1, name: '苹果' }, { id: 2, name: '香蕉' }, { id: 3, name: '橙子' }])</script>
v-on:事件处理
<template> <!-- 完整语法 --> <button v-on:click="handleClick">点击我</button> <!-- 缩写语法 --> <button @click="handleClick">点击我</button> <!-- 事件修饰符 --> <form @submit.prevent="handleSubmit"> <input @keyup.enter="handleEnter"> </form></template><script setup>const handleClick = () => { console.log('按钮被点击了')}const handleSubmit = (event) => { console.log('表单提交', event)}const handleEnter = () => { console.log('按下了Enter键')}</script>
v-model:双向绑定
<template> <!-- 文本输入 --> <input v-model="message" placeholder="输入文本"> <!-- 复选框 --> <input type="checkbox" v-model="isChecked"> <!-- 单选按钮 --> <input type="radio" v-model="selected" value="option1"> <!-- 选择框 --> <select v-model="selectedOption"> <option value="option1">选项1</option> <option value="option2">选项2</option> </select></template><script setup>import { ref } from 'vue'const message = ref('')const isChecked = ref(false)const selected = ref('option1')const selectedOption = ref('option1')</script>
1.3 响应式数据
Vue3 提供了两种创建响应式数据的方式:
ref:用于基本类型数据
<script setup>import { ref } from 'vue'// 创建响应式基本类型const count = ref(0)const message = ref('Hello')const isActive = ref(true)// 修改值需要使用.valueconst increment = () => { count.value++}</script>
reactive:用于对象类型数据
<script setup>import { reactive } from 'vue'// 创建响应式对象const state = reactive({ count: 0, user: { name: '张三', age: 25 }})// 直接修改属性const updateUser = () => { state.user.name = '李四' state.count++}</script>
toRefs:将 reactive 对象转换为 ref 对象
<script setup>import { reactive, toRefs } from 'vue'const state = reactive({ count: 0, message: 'Hello'})// 将reactive对象转换为ref对象const { count, message } = toRefs(state)</script>
组合式 API
组合式 API 是 Vue3 的核心特性,它允许我们将组件逻辑按功能关注点组织,而不是按选项类型组织。

2.1 setup 函数
setup 是组合式 API 的入口点,在组件创建时执行:
<template> <div> <h1>{{ message }}</h1> <p>Count: {{ count }}</p> <button @click="increment">增加</button> </div></template><script setup>import { ref } from 'vue'// setup函数在组件创建时执行const message = ref('Hello Composition API!')const count = ref(0)const increment = () => { count.value++}</script>
2.2 计算属性
使用 computed 创建计算属性:
<script setup>import { ref, computed } from 'vue'const firstName = ref('张')const lastName = ref('三')// 计算属性const fullName = computed(() => { return `${firstName.value}${lastName.value}`})// 可写计算属性const fullName2 = computed({ get() { return `${firstName.value}${lastName.value}` }, set(value) { const [first, last] = value.split(' ') firstName.value = first lastName.value = last }})</script>
2.3 监听器
使用 watch 监听数据变化:
<script setup>import { ref, watch } from 'vue'const count = ref(0)const message = ref('Hello')// 监听单个数据源watch(count, (newValue, oldValue) => { console.log(`count从${oldValue}变为${newValue}`)})// 监听多个数据源watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => { console.log('数据发生变化')})// 监听对象属性const state = ref({ count: 0, message: 'Hello'})watch(() => state.value.count, (newValue) => { console.log(`count变为${newValue}`)})// 深度监听watch(state, (newValue) => { console.log('state发生变化', newValue)}, { deep: true })</script>
2.4 生命周期钩子
在组合式 API 中使用生命周期钩子:
<script setup>import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount} from 'vue'// 组件挂载后onMounted(() => { console.log('组件已挂载')})// 组件更新后onUpdated(() => { console.log('组件已更新')})// 组件卸载前onBeforeUnmount(() => { console.log('组件即将卸载')})// 组件卸载后onUnmounted(() => { console.log('组件已卸载')})</script>
2.5 自定义 Hook
将可复用逻辑封装为自定义 Hook:
// hooks/useCounter.jsimport { ref, computed } from 'vue'export function useCounter(initialValue = 0) { const count = ref(initialValue) const increment = () => { count.value++ } const decrement = () => { count.value-- } const reset = () => { count.value = initialValue } const doubleCount = computed(() => count.value * 2) return { count, doubleCount, increment, decrement, reset }}
使用自定义 Hook:
<template> <div> <p>Count: {{ count }}</p> <p>Double: {{ doubleCount }}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="reset">Reset</button> </div></template><script setup>import { useCounter } from '@/hooks/useCounter'const { count, doubleCount, increment, decrement, reset } = useCounter(0)</script>
Vue Router
Vue Router 是 Vue 官方的路由管理器,用于构建单页应用。

3.1 安装和配置
首先安装 Vue Router:
npm install vue-router@4
创建路由配置文件:
// router/index.jsimport { createRouter, createWebHistory } from 'vue-router'import HomeView from '../views/HomeView.vue'import AboutView from '../views/AboutView.vue'import UserView from '../views/UserView.vue'import NotFoundView from '../views/NotFoundView.vue'const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: AboutView }, { path: '/user/:id', name: 'user', component: UserView, props: true // 将路由参数作为props传递 }, { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView }]const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes})export default router
在 main.js 中使用路由:
// main.jsimport { createApp } from 'vue'import App from './App.vue'import router from './router'createApp(App) .use(router) .mount('#app')
3.2 路由组件
在 App.vue 中添加路由出口:
<template> <div id="app"> <nav> <RouterLink to="/">首页</RouterLink> | <RouterLink to="/about">关于</RouterLink> | <RouterLink :to="{ name: 'user', params: { id: 1 } }">用户1</RouterLink> </nav> <!-- 路由出口 --> <RouterView /> </div></template><script setup>import { RouterLink, RouterView } from 'vue-router'</script>
3.3 路由参数和查询
获取路由参数:
<template> <div> <h1>用户详情</h1> <p>用户ID: {{ userId }}</p> <p>查询参数: {{ query }}</p> </div></template><script setup>import { useRoute } from 'vue-router'const route = useRoute()// 获取路由参数const userId = route.params.id// 获取查询参数const query = route.query</script>
3.4 编程式导航
使用编程方式进行导航:
<template> <div> <button @click="goToHome">返回首页</button> <button @click="goToUser(2)">查看用户2</button> <button @click="goBack">返回</button> </div></template><script setup>import { useRouter } from 'vue-router'const router = useRouter()const goToHome = () => { router.push('/')}const goToUser = (id) => { router.push({ name: 'user', params: { id }, query: { tab: 'profile' } })}const goBack = () => { router.go(-1)}</script>
3.5 路由守卫
使用路由守卫控制页面访问:
// router/index.jsrouter.beforeEach((to, from, next) => { // 检查是否需要登录 const requiresAuth = to.matched.some(record => record.meta.requiresAuth) const isLoggedIn = localStorage.getItem('token') if (requiresAuth && !isLoggedIn) { next('/login') } else { next() }})// 添加需要登录的路由const routes = [ { path: '/dashboard', component: DashboardView, meta: { requiresAuth: true } }]
Pinia 状态管理
Pinia 是 Vue 官方推荐的状态管理库,替代了 Vuex。

4.1 安装和配置
首先安装 Pinia:
npm install pinia
创建 Pinia 实例:
// main.jsimport { 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')
4.2 创建 Store
使用 defineStore 创建 Store:
// stores/counter.jsimport { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', { // 状态 state: () => ({ count: 0, name: 'Vue3' }), // 计算属性 getters: { doubleCount: (state) => state.count * 2, greeting: (state) => `Hello ${state.name}!` }, // 方法 actions: { increment() { this.count++ }, decrement() { this.count-- }, reset() { this.count = 0 }, setName(newName) { this.name = newName } }})
4.3 使用 Store
在组件中使用 Store:
<template> <div> <h1>{{ counterStore.greeting }}</h1> <p>Count: {{ counterStore.count }}</p> <p>Double Count: {{ counterStore.doubleCount }}</p> <button @click="counterStore.increment">+</button> <button @click="counterStore.decrement">-</button> <button @click="counterStore.reset">Reset</button> <input v-model="newName" @keyup.enter="updateName"> </div></template><script setup>import { useCounterStore } from '@/stores/counter'import { ref } from 'vue'// 获取Store实例const counterStore = useCounterStore()const newName = ref('')const updateName = () => { if (newName.value) { counterStore.setName(newName.value) newName.value = '' }}</script>
4.4 修改状态的方法
有多种方式可以修改 Store 状态:
<script setup>import { useCounterStore } from '@/stores/counter'const counterStore = useCounterStore()// 1. 直接修改counterStore.count++// 2. 使用actioncounterStore.increment()// 3. 使用$patch修改多个属性counterStore.$patch({ count: counterStore.count + 1, name: 'Vue3 Updated'})// 4. 使用$patch函数counterStore.$patch((state) => { state.count++ state.name = 'Vue3 Updated'})// 5. 替换整个状态counterStore.$state = { count: 10, name: 'Vue3 Reset'}</script>
4.5 监听 Store 变化
监听 Store 状态变化:
<script setup>import { useCounterStore } from '@/stores/counter'import { watch } from 'vue'const counterStore = useCounterStore()// 监听整个Storewatch( counterStore, (state) => { console.log('Store状态变化:', state) }, { deep: true })// 监听特定属性watch( () => counterStore.count, (newValue, oldValue) => { console.log(`count从${oldValue}变为${newValue}`) })// 使用$subscribe监听counterStore.$subscribe((mutation, state) => { console.log('Mutation:', mutation) console.log('New state:', state)})</script>
4.6 Store 组合
组合多个 Store:
// stores/user.jsimport { defineStore } from 'pinia'export const useUserStore = defineStore('user', { state: () => ({ user: null, isLoggedIn: false }), actions: { login(userData) { this.user = userData this.isLoggedIn = true localStorage.setItem('token', userData.token) }, logout() { this.user = null this.isLoggedIn = false localStorage.removeItem('token') } }})
在另一个 Store 中使用:
// stores/cart.jsimport { defineStore } from 'pinia'import { useUserStore } from './user'export const useCartStore = defineStore('cart', { state: () => ({ items: [] }), getters: { totalItems: (state) => state.items.length, totalPrice: (state) => { return state.items.reduce((total, item) => total + item.price * item.quantity, 0) } }, actions: { addToCart(product, quantity = 1) { const userStore = useUserStore() if (!userStore.isLoggedIn) { throw new Error('请先登录') } const existingItem = this.items.find(item => item.id === product.id) if (existingItem) { existingItem.quantity += quantity } else { this.items.push({ ...product, quantity }) } }, removeFromCart(productId) { this.items = this.items.filter(item => item.id !== productId) }, clearCart() { this.items = [] } }})
TypeScript 集成
Vue3 对 TypeScript 有很好的支持,可以显著提升代码质量和开发效率。

5.1 项目配置
创建支持 TypeScript 的 Vue3 项目:
npm create vite@latest my-vue3-ts-app -- --template vue-ts
配置 tsconfig.json:
{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "moduleResolution": "Node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "paths": { "@/*": ["./src/*"] } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }]}
5.2 组件类型定义
为组件添加类型定义:
<template> <div> <h1>{{ title }}</h1> <p>{{ message }}</p> <button @click="handleClick">点击我</button> </div></template><script setup lang="ts">import { defineProps, defineEmits } from 'vue'// 定义Props类型interface Props { title: string message?: string count?: number}const props = defineProps<Props>({ title: { type: String, required: true }, message: String, count: { type: Number, default: 0 }})// 定义Emits类型const emit = defineEmits<{ 'update:count': [value: number] 'click': [event: MouseEvent]}>()const handleClick = (event: MouseEvent) => { emit('click', event) emit('update:count', (props.count || 0) + 1)}</script>
5.3 响应式数据类型
为响应式数据添加类型:
<script setup lang="ts">import { ref, reactive, computed } from 'vue'// 基本类型const count = ref<number>(0)const message = ref<string>('Hello')const isActive = ref<boolean>(true)// 对象类型interface User { id: number name: string email: string age?: number}const user = ref<User | null>(null)const users = ref<User[]>([])// Reactive对象const state = reactive<{ count: number user: User | null}>({ count: 0, user: null})// 计算属性const doubleCount = computed<number>(() => count.value * 2)const userName = computed<string>(() => user.value?.name || 'Unknown')</script>
5.4 Pinia Store 类型
为 Pinia Store 添加类型:
// stores/user.tsimport { defineStore } from 'pinia'interface User { id: number name: string email: string token: string}interface UserState { user: User | null isLoggedIn: boolean loading: boolean error: string | null}export const useUserStore = defineStore('user', { state: (): UserState => ({ user: null, isLoggedIn: false, loading: false, error: null }), getters: { userName: (state): string => state.user?.name || 'Guest', isAdmin: (state): boolean => state.user?.email === 'admin@example.com' }, actions: { async login(email: string, password: string): Promise<void> { this.loading = true this.error = null try { // 模拟API请求 const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) }) const data = await response.json() if (response.ok) { this.user = data.user this.isLoggedIn = true localStorage.setItem('token', data.token) } else { throw new Error(data.message || '登录失败') } } catch (error) { this.error = error instanceof Error ? error.message : '未知错误' throw error } finally { this.loading = false } }, logout(): void { this.user = null this.isLoggedIn = false localStorage.removeItem('token') } }})
5.5 路由类型
为 Vue Router 添加类型:
// router/index.tsimport { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'import HomeView from '../views/HomeView.vue'import AboutView from '../views/AboutView.vue'import UserView from '../views/UserView.vue'import NotFoundView from '../views/NotFoundView.vue'// 定义路由参数类型interface UserRouteParams { id: string}const routes: RouteRecordRaw[] = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: AboutView }, { path: '/user/:id', name: 'user', component: UserView, props: true }, { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView }]const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes})export default router// 扩展路由类型declare module 'vue-router' { interface RouteMeta { requiresAuth?: boolean title?: string icon?: string }}
在组件中使用类型化的路由:
<script setup lang="ts">import { useRoute, useRouter } from 'vue-router'// 获取类型化的路由const route = useRoute<{ params: { id?: string } query: { tab?: string page?: string }}>()const router = useRouter()// 类型安全的导航const goToUser = (id: number) => { router.push({ name: 'user', params: { id: id.toString() }, query: { tab: 'profile' } })}</script>
实战项目
通过一个完整的实战项目来巩固所学知识。
6.1 项目规划
创建一个简单的待办事项应用(Todo App),包含以下功能:
- 用户认证(登录 / 注册)
- 待办事项管理(添加、编辑、删除、完成)
- 分类和标签
- 数据持久化
6.2 项目结构
todo-app/├── src/│ ├── assets/ # 静态资源│ ├── components/ # 组件│ │ ├── common/ # 通用组件│ │ ├── layout/ # 布局组件│ │ └── todo/ # 业务组件│ ├── composables/ # 组合式函数│ ├── router/ # 路由配置│ ├── stores/ # Pinia Store│ ├── types/ # 类型定义│ ├── utils/ # 工具函数│ ├── views/ # 页面组件│ ├── App.vue # 根组件│ └── main.ts # 入口文件├── package.json├── tsconfig.json└── vite.config.ts
6.3 核心实现
类型定义:
// src/types/index.tsexport interface User { id: string name: string email: string}export interface Todo { id: string title: string description: string completed: boolean createdAt: Date updatedAt: Date tags: string[]}export interface Tag { id: string name: string color: string}
用户 Store:
// src/stores/user.tsimport { defineStore } from 'pinia'import { User } from '@/types'interface UserState { user: User | null isLoggedIn: boolean loading: boolean error: string | null}export const useUserStore = defineStore('user', { state: (): UserState => ({ user: null, isLoggedIn: false, loading: false, error: null }), actions: { async login(email: string, password: string) { this.loading = true this.error = null try { // 模拟API请求 await new Promise(resolve => setTimeout(resolve, 1000)) this.user = { id: '1', name: '张三', email: email } this.isLoggedIn = true localStorage.setItem('user', JSON.stringify(this.user)) } catch (error) { this.error = '登录失败,请检查用户名和密码' throw error } finally { this.loading = false } }, logout() { this.user = null this.isLoggedIn = false localStorage.removeItem('user') }, init() { const userData = localStorage.getItem('user') if (userData) { this.user = JSON.parse(userData) this.isLoggedIn = true } } }})
待办事项 Store:
// src/stores/todo.tsimport { defineStore } from 'pinia'import { Todo } from '@/types'import { useUserStore } from './user'interface TodoState { todos: Todo[] loading: boolean error: string | null}export const useTodoStore = defineStore('todo', { state: (): TodoState => ({ todos: [], loading: false, error: null }), getters: { activeTodos: (state) => state.todos.filter(todo => !todo.completed), completedTodos: (state) => state.todos.filter(todo => todo.completed), totalTodos: (state) => state.todos.length, completedCount: (state) => state.todos.filter(todo => todo.completed).length }, actions: { async fetchTodos() { const userStore = useUserStore() if (!userStore.isLoggedIn) return this.loading = true this.error = null try { // 模拟API请求 await new Promise(resolve => setTimeout(resolve, 1000)) // 从本地存储获取数据 const todosData = localStorage.getItem('todos') if (todosData) { this.todos = JSON.parse(todosData) } else { this.todos = [] } } catch (error) { this.error = '获取待办事项失败' throw error } finally { this.loading = false } }, async addTodo(todo: Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>) { const userStore = useUserStore() if (!userStore.isLoggedIn) return this.loading = true this.error = null try { const newTodo: Todo = { id: Date.now().toString(), ...todo, createdAt: new Date(), updatedAt: new Date() } this.todos.push(newTodo) this.saveTodos() } catch (error) { this.error = '添加待办事项失败' throw error } finally { this.loading = false } }, async updateTodo(id: string, updates: Partial<Todo>) { const userStore = useUserStore() if (!userStore.isLoggedIn) return this.loading = true this.error = null try { const index = this.todos.findIndex(todo => todo.id === id) if (index !== -1) { this.todos[index] = { ...this.todos[index], ...updates, updatedAt: new Date() } this.saveTodos() } } catch (error) { this.error = '更新待办事项失败' throw error } finally { this.loading = false } }, async deleteTodo(id: string) { const userStore = useUserStore() if (!userStore.isLoggedIn) return this.loading = true this.error = null try { this.todos = this.todos.filter(todo => todo.id !== id) this.saveTodos() } catch (error) { this.error = '删除待办事项失败' throw error } finally { this.loading = false } }, saveTodos() { localStorage.setItem('todos', JSON.stringify(this.todos)) } }})
路由配置:
// src/router/index.tsimport { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'import HomeView from '../views/HomeView.vue'import LoginView from '../views/LoginView.vue'import RegisterView from '../views/RegisterView.vue'import TodoListView from '../views/TodoListView.vue'import TodoDetailView from '../views/TodoDetailView.vue'import NotFoundView from '../views/NotFoundView.vue'import { useUserStore } from '@/stores/user'const routes: RouteRecordRaw[] = [ { path: '/', name: 'home', component: HomeView }, { path: '/login', name: 'login', component: LoginView, meta: { requiresAuth: false } }, { path: '/register', name: 'register', component: RegisterView, meta: { requiresAuth: false } }, { path: '/todos', name: 'todo-list', component: TodoListView, meta: { requiresAuth: true } }, { path: '/todos/:id', name: 'todo-detail', component: TodoDetailView, meta: { requiresAuth: true }, props: true }, { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView }]const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes})// 路由守卫router.beforeEach((to, from, next) => { const userStore = useUserStore() const requiresAuth = to.meta.requiresAuth !== false if (requiresAuth && !userStore.isLoggedIn) { next('/login') } else if (!requiresAuth && userStore.isLoggedIn) { next('/todos') } else { next() }})export default router
学习资源推荐
7.1 官方文档
- Vue3 官方文档 – 最权威的学习资源
- Vue Router 官方文档 – 路由管理
- Pinia 官方文档 – 状态管理
- TypeScript 官方文档 – 类型系统
7.2 在线课程
- Vue Mastery – 高质量的 Vue 视频课程
- Vue School – 互动式 Vue 学习平台
- Udemy Vue3 课程 – 付费视频课程
7.3 社区资源
- Vue 论坛 – 官方社区论坛
- Vue DevTools – 开发调试工具
- Vue GitHub – 源代码和问题跟踪
7.4 书籍推荐
- 《Vue.js 设计与实现》- 深入理解 Vue3 内部原理
- 《TypeScript 实战》- 掌握 TypeScript 在 Vue 中的应用
- 《前端架构设计》- 学习大型 Vue 项目的架构设计
总结
通过本指南的学习,你已经掌握了 Vue3 开发的核心技能:
- Vue3 基础语法:模板语法、响应式数据、指令系统
- 组合式 API:setup 函数、计算属性、监听器、生命周期
- Vue Router:路由配置、导航守卫、参数传递
- Pinia:状态管理、Store 组合、数据持久化
- TypeScript:类型定义、类型安全、开发效率
Vue3 作为现代前端开发的重要工具,具有以下优势:
- 性能优秀:重写的响应式系统,性能提升显著
- API 友好:组合式 API 提供了更好的代码组织方式
- 类型安全:原生支持 TypeScript,提升代码质量
- 生态完善:丰富的官方库和第三方插件
- 社区活跃:活跃的开发者社区和丰富的学习资源
建议你通过实际项目来巩固所学知识,从简单的应用开始,逐步挑战更复杂的项目。持续学习和实践是掌握 Vue3 的关键。
祝你在 Vue3 的学习之旅中取得成功!
















