当前位置: 首页 > java >正文

Vue3从入门到精通

Vue3 从入门到精通 🚀

2025年最新版,全面覆盖 Vue3 核心知识、Composition API、高级特性及实践应用。

📚 目录

  • 第一章:Vue3 基础入门
    • 1.1 Vue3 简介
    • 1.2 创建 Vue3 项目
    • 1.3 项目结构
    • 1.4 Vue3 开发基础
    • 1.5 Vue3 基础语法
  • 第二章:Vue3 核心概念
    • 2.1 Composition API 与 Options API
    • 2.2 响应式系统深入理解
    • 2.3 生命周期钩子
    • 2.4 依赖注入
  • 第三章:深入组件
    • 3.1 组件通信
    • 3.2 插槽(Slots)
    • 3.3 动态组件与异步组件
    • 3.4 组件注册与使用
  • 第四章:Vue3 高级特性
    • 4.1 Teleport
    • 4.2 Suspense
    • 4.3 自定义指令
    • 4.4 渲染函数与 JSX
    • 4.5 自定义元素(Web Components)
    • 4.6 Fragments
    • 4.7 TypeScript 集成
  • 第五章:状态管理
  • 第六章:路由管理 (Vue Router)
  • 第七章:实用工具与技巧
  • 第八章:性能优化
  • 第九章:TypeScript 与 Vue
  • 第十章:Vue3 生态系统与最佳实践
  • 第十一章:Vue3 集成 Ant Design Vue
  • 第十二章:基于 Vue3 和 Ant Design Vue 的企业实战
  • 第十三章:Vue3 魔法语法大揭秘 🧙‍♂️
  • 第十四章:Vue3 纯语法讲解
  • 第九章:Vue3 高级组件模式
    • 9.1 组件通信模式
      • 9.1.1 事件总线模式
      • 9.1.2 依赖注入模式
    • 9.2 高级组件模式
      • 9.2.1 高阶组件模式
      • 9.2.2 组合式函数模式
    • 9.3 性能优化模式
      • 9.3.1 虚拟滚动
      • 9.3.2 懒加载组件
    • 9.4 状态管理模式
      • 9.4.1 简单的状态管理
      • 9.4.2 组合式状态管理

快速导航

基础篇进阶篇实战篇
基础入门核心概念状态管理
项目创建组件通信路由管理
基础语法高级特性性能优化

学习提示

💡 提示:本教程采用渐进式学习方式,建议按顺序阅读。

⚠️ 注意:代码示例中的注释和说明都很重要,请仔细阅读。

🔥 重点:每个章节末尾都有实践练习,建议动手实践。


第一章:Vue3 基础入门

1.1 Vue3 简介

Vue3 是一个流行的前端框架,由尤雨溪(Evan You)领导开发,于2020年9月正式发布。相比 Vue2,Vue3 具有以下优势:

特性说明优势
✨ 性能重写虚拟 DOM 实现和编译优化更快的渲染速度和更小的包体积
📦 包体积支持树摇(Tree-shaking)优化更小的打包体积
🔧 TypeScript内置类型声明和类型推断更好的开发体验
🧩 Composition API提供更灵活的代码组织方式更好的代码复用性
🚀 新特性Teleport、Fragments、Suspense 等更强大的功能支持

1.2 创建 Vue3 项目

# 使用 Vite (推荐)
npm create vite@latest my-vue-app -- --template vue# 或使用 Vue CLI
npm install -g @vue/cli
vue create my-vue-app

💡 提示:Vite 是一个现代前端构建工具,提供极快的开发服务器启动和即时的模块热更新,是 Vue 团队推荐的官方构建工具。

1.3 项目结构

标准的 Vue3 项目结构如下:

my-vue-app/
├── node_modules/     # 依赖包
├── public/           # 静态资源目录
├── src/              # 源代码目录
│   ├── assets/       # 资源文件
│   ├── components/   # 组件目录
│   ├── App.vue       # 根组件
│   └── main.js       # 入口文件
├── package.json      # 项目配置
├── README.md         # 文档
├── index.html        # HTML 模板
└── vite.config.js    # Vite 配置

📋 说明src 目录是主要的开发目录,包含所有源代码。通常还会根据项目需求添加 viewsrouterstore 等目录。

1.4 Vue3 开发基础

1.4.1 创建第一个组件

Vue 组件由三部分组成:<template><script><style>。下面是一个基本的 Vue3 组件示例:

<!-- App.vue -->
<template><view class="app"><text class="title">{{ greeting }}</text><button @click="changeGreeting">更改问候语</button></view>
</template><script setup>
import { ref } from 'vue'// 响应式状态
const greeting = ref('你好,Vue3!')// 方法
function changeGreeting() {greeting.value = '你好,世界!'
}
</script><style scoped>
.app {font-family: Arial, sans-serif;text-align: center;margin-top: 60px;
}.title {font-size: 24px;color: #333;
}
</style>

🔑 关键点:Vue3 推荐使用 <script setup> 语法,它是组合式 API 的编译时语法糖,使代码更简洁。同时,使用语义化的 viewtext 组件替代 divspan 可以提高代码的可读性和可维护性。

1.4.2 组件注册与使用

在 Vue3 中注册和使用组件:

<!-- 父组件 -->
<template><view class="container"><text class="header">父组件</text><HelloWorld /></view>
</template><script setup>
import HelloWorld from './components/HelloWorld.vue'
// 使用 script setup 时,导入的组件会自动注册
</script>
1.4.3 应用挂载

main.js 中创建并挂载 Vue 应用:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'// 创建应用实例
const app = createApp(App)// 挂载到DOM
app.mount('#app')

1.5 Vue3 基础语法

1.5.1 模板语法

Vue 使用基于 HTML 的模板语法:

  • 文本插值:{{ 变量 }}
  • 属性绑定::属性名="变量"(也可写作 v-bind:属性名
  • 条件渲染:v-ifv-else-ifv-else
  • 循环渲染:v-for
  • 事件处理:@事件名="方法"(也可写作 v-on:事件名

📝 示例:下面的代码展示了常见的模板语法用法。

<template><!-- 文本插值 --><p>{{ message }}</p><!-- 属性绑定 --><img :src="imageUrl" :alt="altText"><!-- 条件渲染 --><p v-if="isLoggedIn">欢迎回来!</p><p v-else>请登录。</p><!-- 循环渲染 --><ul><li v-for="item in items" :key="item.id">{{ item.name }}</li></ul><!-- 事件处理 --><button @click="handleClick">点击我</button>
</template>
1.5.2 响应式基础

Vue3 的响应式系统基于 Proxy,提供了以下 API:

  • ref:创建基本类型的响应式引用
  • reactive:创建对象的响应式代理
  • computed:创建计算属性
  • watch / watchEffect:监听响应式数据变化
<script setup>
import { ref, reactive, computed, watch } from 'vue'// ref 用于简单值
const count = ref(0)
// 在 JS 中访问或修改需要 .value
console.log(count.value)
count.value++// reactive 用于对象
const user = reactive({name: '张三',age: 25
})
// 直接访问属性,无需 .value
console.log(user.name)
user.age++// computed 计算属性
const doubleCount = computed(() => count.value * 2)// watch 监听变化
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})
</script>

⚠️ 注意:使用 ref 创建的响应式数据在模板中可以直接使用,但在 JavaScript 中必须通过 .value 访问或修改。


第二章:Vue3 核心概念

本章概述:本章深入探讨 Vue3 的核心概念,包括 Composition API、响应式系统、生命周期钩子和依赖注入等重要特性。

2.1 Composition API 与 Options API

Vue3 提供了两种编写组件的方式:传统的 Options API 和新引入的 Composition API。

2.1.1 Options API

Options API 将组件的逻辑分散到不同选项中:

<script>
export default {// 状态data() {return { count: 0 }},// 计算属性computed: {doubleCount() { return this.count * 2 }},// 方法methods: {increment() { this.count++ }},// 生命周期mounted() {console.log('组件已挂载')}
}
</script>

🔍 分析:Options API 将相关逻辑分散在不同选项中。随着组件复杂度增加,相关逻辑分散在各处,导致代码难以维护。

2.1.2 Composition API

Composition API 允许我们按功能/逻辑关注点组织代码:

<script setup>
import { ref, computed, onMounted } from 'vue'// 计数器逻辑
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {count.value++
}// 生命周期
onMounted(() => {console.log('组件已挂载')
})
</script>

💪 优势:相关逻辑可以放在一起,即使组件变大,相关功能的代码也能保持集中。

Composition API 的核心优势:

  • 更好的代码组织:相关逻辑可以放在一起
  • 更好的逻辑复用:可以轻松提取和复用逻辑
  • 更好的类型推断:对 TypeScript 更友好
  • 更小的包体积:更好的代码压缩效果

2.2 响应式系统深入理解

Vue3 的响应式系统基于 JavaScript 的 Proxy 实现,这使得它比 Vue2 的 Object.defineProperty 更强大。

2.2.1 ref 和 reactive
import { ref, reactive, isRef, isReactive } from 'vue'// ref 用于基本类型和对象类型
const count = ref(0)           // 基本类型用 ref
const user = ref({ name: '张三' }) // 对象也可以用 ref// 访问和修改 ref 值需要 .value
console.log(count.value) // 0
count.value++
console.log(user.value.name) // 张三// reactive 只用于对象类型
const state = reactive({name: '李四',age: 25,address: {city: '北京'     // 嵌套对象自动转换为响应式}
})// reactive 对象可以直接访问和修改
console.log(state.name) // 李四
state.age++
state.address.city = '上海'// 类型检查
console.log(isRef(count)) // true
console.log(isReactive(state)) // true

📌 使用建议

  • 对于基本类型(字符串、数字、布尔值),使用 ref
  • 对于复杂数据结构(对象、数组),优先使用 reactive
  • 如果要对整个对象进行替换,使用 ref 存储对象
2.2.2 计算属性
import { ref, computed } from 'vue'const count = ref(0)// 只读计算属性
const doubleCount = computed(() => count.value * 2)
// doubleCount.value = 10 // ❌ 错误,只读计算属性不能被赋值// 可写计算属性
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({// getterget() {return firstName.value + lastName.value},// setterset(newValue) {[firstName.value, lastName.value] = newValue.split(' ')}
})console.log(fullName.value) // 张三
fullName.value = '李 四'    // 触发 setter
console.log(firstName.value) // 李
console.log(lastName.value) // 四

⚡ 性能提示:计算属性会缓存结果,只有当依赖项变化时才会重新计算。相比在模板或方法中直接使用表达式更高效。

2.2.3 侦听器
import { ref, reactive, watch, watchEffect } from 'vue'const count = ref(0)
const user = reactive({ name: '张三', age: 25 })// 1. 侦听单个数据源
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`)
}, { immediate: true,  // 立即执行一次回调deep: true,       // 深度侦听对象内部变化flush: 'post'     // DOM 更新后触发回调
})// 2. 侦听多个数据源
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => {console.log(`count: ${oldCount} -> ${newCount}, age: ${oldAge} -> ${newAge}`)
})// 3. 侦听响应式对象的属性
watch(() => user.name, (newName, oldName) => {console.log(`name 从 ${oldName} 变为 ${newName}`)
})// 4. watchEffect 自动收集依赖并执行
watchEffect(() => {// 自动追踪所有使用的响应式数据console.log(`count: ${count.value}, name: ${user.name}`)
})// 触发侦听器
count.value++
user.name = '李四'

📊 对比

  • watch 需要明确指定要侦听的数据,可以访问变化前后的值
  • watchEffect 自动追踪内部依赖,但无法直接访问变化前的值
  • 当需要比较变化前后的值时,使用 watch
  • 当只关心响应式状态的"当前值"而不关心"变化前的值",用 watchEffect

2.3 生命周期钩子

Vue3 的生命周期钩子使用基于函数的 API:

<script setup>
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,onActivated,onDeactivated,onErrorCaptured
} from 'vue'// --- 组件创建阶段 ---
onBeforeMount(() => {console.log('在挂载前执行 - DOM尚未创建')
})onMounted(() => {console.log('组件已挂载 - DOM已创建,可以访问DOM节点')// 适合进行数据获取、添加DOM事件监听器等
})// --- 组件更新阶段 ---
onBeforeUpdate(() => {console.log('在更新前执行 - 数据已变化,但DOM尚未更新')
})onUpdated(() => {console.log('组件已更新 - DOM已更新')// 注意:避免在此钩子中修改状态,可能导致无限循环
})// --- 组件销毁阶段 ---
onBeforeUnmount(() => {console.log('在卸载前执行 - 组件实例仍然完全可用')// 适合清理定时器、事件监听器等资源
})onUnmounted(() => {console.log('组件已卸载 - 所有子组件也已卸载')
})// --- 缓存组件相关(配合 <KeepAlive> 使用)---
onActivated(() => {console.log('缓存组件被激活')
})onDeactivated(() => {console.log('缓存组件被停用')
})// --- 错误处理 ---
onErrorCaptured((err, instance, info) => {console.log('捕获到后代组件错误', err, info)return false // 阻止错误继续向上传播
})
</script>

🔄 生命周期图示

创建 -> 挂载 -> 更新 -> 卸载
  1. 创建: setup() (替代了 beforeCreate 和 created)
  2. 挂载: onBeforeMount -> onMounted
  3. 更新: onBeforeUpdate -> onUpdated
  4. 卸载: onBeforeUnmount -> onUnmounted

生命周期对比表:

Vue2 选项 APIVue3 Composition API
beforeCreatesetup()
createdsetup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated
errorCapturedonErrorCaptured

2.4 依赖注入

Vue3 提供了依赖注入机制,使组件树中的数据传递更加便捷:

<!-- 父组件提供数据 -->
<script setup>
import { ref, provide } from 'vue'// 创建提供的数据
const theme = ref('light')
const toggleTheme = () => {theme.value = theme.value === 'light' ? 'dark' : 'light'
}// 提供值和修改该值的方法
provide('theme', theme)                // ✅ 提供响应式数据
provide('toggleTheme', toggleTheme)    // ✅ 提供方法// ❌ 反模式: 提供非响应式数据
// provide('theme', 'light')           // 后代组件无法感知变化
</script><!-- 后代组件注入数据 -->
<script setup>
import { inject, ref } from 'vue'// 注入值和方法
const theme = inject('theme')             // 获取主题
const toggleTheme = inject('toggleTheme') // 获取切换方法// 使用默认值注入(当提供者没有提供时使用默认值)
const fontSize = inject('fontSize', ref('16px'))
</script>

🔗 最佳实践

  1. 总是使用响应式数据作为提供值,这样后代组件能响应变化
  2. 使用 Symbol 作为注入名称,避免名称冲突
  3. 考虑创建单独的组合式函数封装 provide/inject 逻辑

依赖注入最适合用于以下场景:

  • 🎨 组件库的主题、配置等全局设置
  • 📡 跨越多层级的数据传递
  • 🔌 插件与应用之间的通信

第三章:深入组件

本章概述:本章深入讲解 Vue3 组件的核心功能,包括多种组件通信方式、插槽系统、动态/异步组件的使用以及组件注册的最佳实践。

3.1 组件通信

Vue3 提供了多种组件间通信方式,可以根据不同场景选择最合适的方式。

🧩 组件通信一览表

通信方式适用场景是否响应式复杂度
Props父传子简单
Emits子传父简单
v-model双向绑定简单
Provide/Inject跨层级传递中等
EventBus任意组件间中等
Vuex/Pinia全局状态管理复杂
3.1.1 Props(父传子)

Props 是父组件向子组件传递数据的主要方式:

<!-- 父组件 -->
<template><ChildComponent :title="pageTitle" :user="currentUser" :items="itemList"/>
</template><script setup>
import { ref, reactive } from 'vue'
import ChildComponent from './ChildComponent.vue'const pageTitle = ref('用户列表')
const currentUser = reactive({ id: 1, name: '张三' })
const itemList = ref(['项目1', '项目2', '项目3'])
</script><!-- 子组件 (ChildComponent.vue) -->
<template><div><h1>{{ title }}</h1><p>当前用户: {{ user.name }}</p><ul><li v-for="(item, index) in items" :key="index">{{ item }}</li></ul></div>
</template><script setup>
// 方式 1: 使用 defineProps 宏
const props = defineProps({title: String,user: Object,items: Array
})// 方式 2: 使用类型声明
// const props = defineProps<{
//   title: string;
//   user: { id: number; name: string };
//   items: string[];
// }>()// 方式 3: 带默认值的类型声明
// const props = withDefaults(defineProps<{
//   title: '默认标题',
//   items: () => []
// })
</script>

⚠️ 注意:Props 是单向下行绑定的 - 父组件的属性变化会传递给子组件,但子组件不能直接修改 props(单向数据流)。

3.1.2 Emits(子传父)

子组件可以通过 emits 向父组件发送事件和数据:

<!-- 子组件 (ChildComponent.vue) -->
<template><div><input v-model="inputValue" /><button @click="sendMessage">发送消息</button><button @click="$emit('increment', 1)">直接发送事件</button></div>
</template><script setup>
import { ref } from 'vue'// 声明组件可触发的事件
const emit = defineEmits(['message', 'increment', 'update'])// 或使用类型声明
// const emit = defineEmits<{
//   (e: 'message', text: string): void;
//   (e: 'increment', value: number): void;
//   (e: 'update', data: { id: number, value: string }): void;
// }>()const inputValue = ref('')function sendMessage() {if (inputValue.value.trim()) {// 触发事件并传递数据emit('message', inputValue.value)inputValue.value = ''}
}
</script><!-- 父组件 -->
<template><div><ChildComponent @message="handleMessage" @increment="incrementCount" /><p>最新消息: {{ latestMessage }}</p><p>计数: {{ count }}</p></div>
</template><script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'const latestMessage = ref('')
const count = ref(0)function handleMessage(message) {latestMessage.value = message
}function incrementCount(value) {count.value += value
}
</script>

💡 提示:使用 defineEmits 声明组件可能触发的所有事件,这有助于更好的文档和类型检查。

3.1.3 v-model 双向绑定

Vue3 改进了 v-model,支持多个数据的双向绑定:

<!-- 父组件 -->
<template><UserForm v-model:name="userData.name" v-model:email="userData.email" /><div><p>姓名: {{ userData.name }}</p><p>邮箱: {{ userData.email }}</p></div>
</template><script setup>
import { reactive } from 'vue'
import UserForm from './UserForm.vue'const userData = reactive({name: '张三',email: 'zhangsan@example.com'
})
</script><!-- 子组件 (UserForm.vue) -->
<template><div><div><label>姓名:</label><input :value="name" @input="$emit('update:name', $event.target.value)"/></div><div><label>邮箱:</label><input :value="email" @input="$emit('update:email', $event.target.value)"/></div></div>
</template><script setup>
defineProps({name: String,email: String
})defineEmits(['update:name', 'update:email'])
</script>

🔄 变化:Vue3 的 v-model 变化

  1. 默认 prop 名从 value 改为 modelValue
  2. 默认事件名从 input 改为 update:modelValue
  3. 新增 v-model:propName 语法支持多个 v-model 绑定
  4. 移除了 .sync 修饰符(使用 v-model:propName 替代)
3.1.4 Provide/Inject(跨层级通信)

Provide/Inject 适用于跨多级组件传递数据:

<!-- 根组件 -->
<script setup>
import { ref, provide } from 'vue'// 全局设置
const globalSettings = ref({theme: 'light',fontSize: 16
})// 提供响应式数据
provide('settings', globalSettings)// 提供更新函数
function updateTheme(theme) {globalSettings.value.theme = theme
}
provide('updateTheme', updateTheme)
</script><!-- 中间组件 - 不需要关心数据传递 -->
<template><div><slot></slot></div>
</template><!-- 深层子组件 -->
<template><div :class="settings.theme"><h3>当前主题: {{ settings.theme }}</h3><button @click="toggleTheme">切换主题</button></div>
</template><script setup>
import { inject } from 'vue'// 注入数据和更新函数
const settings = inject('settings')
const updateTheme = inject('updateTheme')function toggleTheme() {const newTheme = settings.theme === 'light' ? 'dark' : 'light'updateTheme(newTheme)
}
</script>

📝 最佳实践: 创建一个专门的组合式函数来封装 provide/inject 逻辑,使其更易复用:

// useTheme.js
import { ref, provide, inject } from 'vue'// 使用 Symbol 作为 key 避免命名冲突
const themeSymbol = Symbol('theme')export function provideTheme() {const theme = ref('light')function setTheme(newTheme) {theme.value = newTheme}provide(themeSymbol, {theme,setTheme})
}export function useTheme() {const theme = inject(themeSymbol)if (!theme) {throw new Error('useTheme() 必须在 provideTheme() 之后使用')}return theme
}

3.2 插槽(Slots)

插槽是 Vue 中实现内容分发的主要机制,Vue3 支持三种类型的插槽。

3.2.1 默认插槽
<!-- BaseButton.vue -->
<template><button class="base-button"><slot>默认内容</slot></button>
</template><!-- 使用默认插槽 -->
<template><BaseButton>点击我</BaseButton><BaseButton><span>自定义内容</span><i class="icon"></i></BaseButton><BaseButton /> <!-- 使用默认内容 -->
</template>

🧩 示意图: 插槽就像组件中的"内容占位符",可以被父组件传入的内容替换。

3.2.2 命名插槽
<!-- BaseCard.vue -->
<template><div class="card"><header class="card-header"><slot name="header">默认标题</slot></header><main class="card-body"><slot>默认内容</slot></main><footer class="card-footer"><slot name="footer">默认底部</slot></footer></div>
</template><!-- 使用命名插槽 -->
<template><BaseCard><template #header><h3>用户信息</h3></template><template #default><p>这里是卡片的主要内容</p></template><template #footer><button>保存</button><button>取消</button></template></BaseCard>
</template>

🔍 语法变化:Vue3 中 v-slot:name 可以简写为 #name,更加简洁。

3.2.3 作用域插槽

作用域插槽允许子组件向父组件传递数据:

<!-- DataList.vue -->
<template><ul><li v-for="(item, index) in items" :key="item.id"><slot :item="item" :index="index" :selected="selectedId === item.id">{{ item.name }}</slot></li></ul>
</template><script setup>
import { ref } from 'vue'defineProps({items: {type: Array,required: true}
})const selectedId = ref(1)
</script><!-- 使用作用域插槽 -->
<template><DataList :items="users"><template #default="{ item, index, selected }"><div :class="{ selected }">{{ index + 1 }}. {{ item.name }}<span v-if="selected">(当前选中)</span></div></template></DataList>
</template><script setup>
import DataList from './DataList.vue'const users = [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' }
]
</script>

🎯 使用场景:作用域插槽非常适合创建灵活的列表组件,表格组件或需要基于子组件内部状态渲染内容的任何场景。

3.3 动态组件与异步组件

3.3.1 动态组件

动态组件使用 <component> 元素和 :is 属性实现:

<template><div><div class="tabs"><button v-for="tab in tabs" :key="tab.name"@click="currentTab = tab.component":class="{ active: currentTab === tab.component }">{{ tab.name }}</button></div><div class="tab-content"><!-- 动态组件 --><component :is="currentTab"></component></div></div>
</template><script setup>
import { ref, markRaw, shallowRef } from 'vue'
import Home from './tabs/Home.vue'
import Profile from './tabs/Profile.vue'
import Settings from './tabs/Settings.vue'// 使用 shallowRef 性能更好
const currentTab = shallowRef(Home)const tabs = [{ name: '首页', component: markRaw(Home) },{ name: '个人资料', component: markRaw(Profile) },{ name: '设置', component: markRaw(Settings) }
]
</script>

⚡ 性能提示

  • 使用 markRaw 防止组件对象被响应式系统代理,提高性能
  • 使用 shallowRef 而不是 ref 来存储组件,避免深层响应式转换
  • 使用 <KeepAlive> 可以缓存非活动组件,保留其状态
3.3.2 异步组件

异步组件可以延迟加载,提高应用性能:

<script setup>
import { defineAsyncComponent, ref, shallowRef } from 'vue'// 基本用法
const AsyncComponent = defineAsyncComponent(() => import('./HeavyComponent.vue')
)// 高级配置
const AsyncComponentWithOptions = defineAsyncComponent({// 加载函数loader: () => import('./HeavyComponent.vue'),// 加载中显示的组件loadingComponent: defineAsyncComponent(() => import('./LoadingComponent.vue')),// 错误时显示的组件errorComponent: defineAsyncComponent(() => import('./ErrorComponent.vue')),// 显示加载组件前的延迟时间,默认为 200msdelay: 200,// 超时时间,超时后显示错误组件timeout: 3000,// 在显示 loadingComponent 组件前是否显示内嵌元素suspensible: false,// 加载失败时调用的函数onError(error, retry, fail) {if (error.message.match(/fetch/)) {// 网络错误,重试retry()} else {// 其他错误,跳转到失败状态fail()}}
})// 用于动态切换的组件
const currentPage = shallowRef(AsyncComponent)
</script>

📊 异步组件优势

  1. 更小的初始包体积:组件按需加载,减少首次加载时间
  2. 更快的应用启动:初始渲染仅包含必要组件
  3. 更好的资源分配:按用户操作延迟加载功能

3.4 组件注册与使用

Vue3 组件可以全局注册或局部注册:

// main.js - 全局注册
import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'
import BaseCard from './components/BaseCard.vue'const app = createApp(App)// 全局注册组件
app.component('BaseButton', BaseButton)
app.component('BaseCard', BaseCard)app.mount('#app')

使用 <script setup> 时的局部注册:

<script setup>
// 局部注册组件 - 导入即可使用
import BaseButton from './components/BaseButton.vue'
import BaseCard from './components/BaseCard.vue'// 在 <script setup> 中导入的组件会自动注册,无需额外配置
</script><template><div><BaseButton>点击我</BaseButton><BaseCard>卡片内容</BaseCard></div>
</template>

不使用 <script setup> 时的局部注册:

<script>
import BaseButton from './components/BaseButton.vue'
import BaseCard from './components/BaseCard.vue'export default {components: {BaseButton,BaseCard}
}
</script>

🧠 组件注册最佳实践:

  1. 全局注册:适用于在整个应用中频繁使用的基础组件(按钮、卡片、输入框等)
  2. 局部注册:适用于特定页面或功能的组件,避免全局污染
  3. 自动注册:可以创建一个工具函数自动注册某个目录下的所有基础组件

第四章:Vue3 高级特性

4.1 Teleport

Teleport 是 Vue3 新引入的功能,允许将组件的一部分 DOM 传送到组件外部的 DOM 节点中渲染。这对于模态框、弹出菜单等需要突破父组件 CSS 限制的场景非常有用。

<template><div class="container"><h2>Teleport 示例</h2><button @click="showModal = true">打开模态框</button><!-- 使用 Teleport 将模态框内容传送到 body 标签 --><Teleport to="body"><div v-if="showModal" class="modal"><div class="modal-content"><h3>模态框标题</h3><p>这个模态框渲染在 body 标签中,不受父组件 CSS 影响</p><button @click="showModal = false">关闭</button></div></div></Teleport></div>
</template><script setup>
import { ref } from 'vue'const showModal = ref(false)
</script><style scoped>
.container {position: relative;overflow: hidden; /* 即使有这个限制,模态框也不会被裁剪 */
}.modal {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: 100;
}.modal-content {background-color: white;padding: 20px;border-radius: 8px;min-width: 300px;
}
</style>

Teleport 的主要用途:

  • 模态框和对话框
  • 悬浮提示和菜单
  • 全局通知
  • CSS 限制场景(如 overflow: hiddenz-index 上下文等)

4.2 Suspense

Suspense 是一个内置组件,用于协调对异步依赖的处理,可以在等待异步组件或异步 setup 函数完成时显示加载状态。

<template><Suspense><!-- 主要内容 --><template #default><AsyncComponent /></template><!-- 加载中显示的内容 --><template #fallback><div class="loading"><div class="spinner"></div><p>正在加载数据...</p></div></template></Suspense>
</template><script setup>
import { defineAsyncComponent } from 'vue'// 异步组件
const AsyncComponent = defineAsyncComponent(() => import('./components/DataDisplay.vue')
)
</script>

在组件中使用异步 setup 函数:

<!-- DataDisplay.vue -->
<template><div><h2>用户数据</h2><ul><li v-for="user in users" :key="user.id">{{ user.name }}</li></ul></div>
</template><script setup>
// 在 setup 函数中使用 async/await
// 这会使组件成为异步组件
const users = await fetchUsers()async function fetchUsers() {const response = await fetch('https://jsonplaceholder.typicode.com/users')return response.json()
}
</script>

Suspense 的错误处理可以配合 onErrorCaptured 生命周期钩子使用:

<script setup>
import { onErrorCaptured, ref } from 'vue'const error = ref(null)onErrorCaptured((e) => {error.value = ereturn false // 阻止错误继续传播
})
</script><template><div v-if="error">发生错误: {{ error.message }}</div><Suspense v-else><!-- 内容和回退内容 --></Suspense>
</template>

4.3 自定义指令

Vue3 中自定义指令的 API 设计与组件生命周期钩子保持一致,提供更好的一致性。

4.3.1 注册全局自定义指令
// main.js
import { createApp } from 'vue'
import App from './App.vue'const app = createApp(App)// 聚焦指令 v-focus
app.directive('focus', {mounted(el) {el.focus()}
})// 点击外部指令 v-click-outside
app.directive('click-outside', {mounted(el, binding) {el._clickOutside = (event) => {if (!(el === event.target || el.contains(event.target))) {binding.value(event)}}document.body.addEventListener('click', el._clickOutside)},unmounted(el) {document.body.removeEventListener('click', el._clickOutside)}
})app.mount('#app')
4.3.2 在组件中注册局部指令
<template><input v-focus placeholder="自动获得焦点" /><div v-highlight:warn="'警告文本'">高亮文本</div>
</template><script setup>
import { ref, reactive, directive } from 'vue'// 使用 vueuse 的实用工具创建自定义指令
const vFocus = {mounted: (el) => el.focus()
}// 自定义高亮指令
const vHighlight = {mounted(el, binding) {// binding.value: 传递给指令的值// binding.arg: 指令参数// binding.modifiers: 修饰符对象const color = binding.arg === 'warn' ? 'orange' :binding.arg === 'error' ? 'red' : 'green'el.style.backgroundColor = colorel.style.color = 'white'if (binding.value) {el.textContent = binding.value}}
}
</script>
4.3.3 自定义指令的钩子函数

Vue3 中的指令钩子与组件生命周期保持一致:

  • created: 在元素的 attribute 或事件监听器被应用之前调用
  • beforeMount: 指令第一次绑定到元素且挂载父组件之前调用
  • mounted: 元素插入到父组件时调用
  • beforeUpdate: 在包含组件的 VNode 更新之前调用
  • updated: 包含组件的 VNode 及其子组件的 VNode 更新之后调用
  • beforeUnmount: 在卸载绑定元素的父组件之前调用
  • unmounted: 当指令与元素解除绑定且父组件已卸载时调用

4.4 渲染函数与 JSX

Vue3 的渲染函数相比 Vue2 有较大变化,提供了更简洁的 API。

4.4.1 使用 h 函数创建虚拟 DOM
<script setup>
import { h, ref } from 'vue'const count = ref(0)// 返回一个渲染函数
function renderButton() {return h('button', { onClick: () => count.value++ }, [`点击次数: ${count.value}`])
}// 在 setup 中定义渲染函数
defineRender(() => {return h('div', [h('h1', 'Hello Render Function!'),renderButton()])
})
</script>

也可以使用更传统的方式定义渲染函数:

<script>
import { h, ref } from 'vue'export default {setup() {const count = ref(0)return () => h('div', [h('h1', 'Hello Render Function!'),h('button', { onClick: () => count.value++ }, [`点击次数: ${count.value}`])])}
}
</script>
4.4.2 使用 JSX

如果项目配置了 JSX 支持,可以直接使用 JSX 语法编写更直观的渲染逻辑:

<script>
import { ref } from 'vue'export default {setup() {const count = ref(0)const increment = () => count.value++return () => (<div><h1>Hello JSX!</h1><button onClick={increment}>点击次数: {count.value}</button></div>)}
}
</script>
4.4.3 何时使用渲染函数

虽然模板更容易编写和理解,但在以下场景中渲染函数可能更合适:

  • 需要完全的 JavaScript 编程能力来构建复杂的动态界面
  • 编写高度抽象的可复用组件,特别是需要动态生成组件结构的场景
  • 实现特殊的表单元素或可视化组件
  • 实现自定义的过渡效果或动画

4.5 自定义元素(Web Components)

Vue3 提供了与 Web Components 的良好集成。

4.5.1 在 Vue 应用中使用自定义元素
// main.js
import { createApp } from 'vue'
import App from './App.vue'// 创建 Vue 应用并配置自定义元素选项
const app = createApp(App)// 告诉 Vue 编译器跳过对这些元素的校验
app.config.compilerOptions.isCustomElement = tag => {return tag.startsWith('my-') || tag.includes('-')
}app.mount('#app')

然后在组件中使用自定义元素:

<template><div><my-web-component :data="componentData" @custom-event="handleEvent"></my-web-component></div>
</template><script setup>
import { ref } from 'vue'
import '@my-org/web-components'const componentData = ref({ name: '自定义数据' })function handleEvent(event) {console.log('自定义元素事件:', event.detail)
}
</script>
4.5.2 将 Vue 组件定义为自定义元素

Vue 提供了 defineCustomElement 方法,可以将 Vue 组件转换为原生自定义元素:

// CustomButton.ce.js
import { defineCustomElement } from 'vue'// 定义组件
const CustomButton = defineCustomElement({props: {type: { type: String, default: 'primary' },label: String},emits: ['click'],template: `<button :class="['btn', 'btn-' + type]" @click="$emit('click')">{{ label }}</button>`,styles: [`.btn { padding: 8px 16px; border-radius: 4px; }.btn-primary { background-color: #3490dc; color: white; }.btn-danger { background-color: #e3342f; color: white; }`]
})// 注册自定义元素
customElements.define('custom-button', CustomButton)

然后在任何 HTML 页面或其他框架中使用:

<custom-button type="danger" label="删除"></custom-button><script>document.querySelector('custom-button').addEventListener('click', () => {alert('按钮被点击了!')})
</script>

4.6 Fragments

Vue3 支持多根节点组件(片段),不再需要在模板中添加单一的根元素:

<template><!-- Vue3 中可以有多个根节点 --><header>页面头部</header><main>页面内容</main><footer>页面底部</footer>
</template>

4.7 TypeScript 集成

Vue3 由 TypeScript 编写,提供了一流的类型支持。

4.7.1 在 Props 中使用类型
<script setup lang="ts">
import { ref, computed } from 'vue'// 使用类型定义 Props
interface Props {title: stringcount?: numberitems: string[]user: {id: numbername: string}status: 'active' | 'inactive'
}// 使用 TS 语法定义 props (推荐)
const props = defineProps<Props>()// 带默认值的类型定义
// const props = withDefaults(defineProps<Props>(), {
//   count: 0,
//   items: () => ['默认项'],
// })
</script>
4.7.2 为事件添加类型
<script setup lang="ts">
// 定义事件类型
interface Emits {(e: 'update', id: number): void(e: 'delete', id: number, confirm: boolean): void(e: 'select', items: string[]): void
}const emit = defineEmits<Emits>()// 正确使用类型化的 emit
emit('update', 123)  // 正确
emit('delete', 123, true)  // 正确
// emit('update')  // 类型错误: 缺少必需的参数
</script>
4.7.3 类型化的 ref 和 reactive
<script setup lang="ts">
import { ref, reactive } from 'vue'// 自动类型推断
const count = ref(0)  // 类型为 Ref<number>
const active = ref(false)  // 类型为 Ref<boolean>// 显式类型标注
const name = ref<string>('')
const list = ref<string[]>([])// 复杂类型
interface User {id: numbername: stringemail: stringactive: boolean
}// 显式类型注解
const user = reactive<User>({id: 1,name: '张三',email: 'zhangsan@example.com',active: true
})// 或让 TypeScript 推断类型
const userInfo: User = {id: 1,name: '张三',email: 'zhangsan@example.com',active: true
}
const reactiveUser = reactive(userInfo)
</script>

第五章:状态管理

5.1 Pinia (Vue3官方推荐的状态管理)

安装Pinia:

npm install pinia

定义store:

// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})// 也可以使用Composition API写法
export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})

在组件中使用:

<template><div><p>Count: {{ counter.count }}</p><p>Double: {{ counter.doubleCount }}</p><button @click="counter.increment()">增加</button></div>
</template><script setup>
import { useCounterStore } from '../stores/counter'const counter = useCounterStore()
</script>

5.2 Vuex (向前兼容)

虽然Pinia是Vue3官方推荐的状态管理解决方案,但Vuex仍然兼容Vue3:

// store.js
import { createStore } from 'vuex'export default createStore({state: {count: 0},getters: {doubleCount: state => state.count * 2},mutations: {increment(state) {state.count++}},actions: {incrementAsync({ commit }) {setTimeout(() => {commit('increment')}, 1000)}}
})

第六章:路由管理 (Vue Router)

6.1 基本用法

// router.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'const router = createRouter({history: createWebHistory(),routes: [{ path: '/', component: Home },{ path: '/about', component: About },{ path: '/user/:id', component: () => import('./views/User.vue'),props: true},{path: '/:pathMatch(.*)*',name: 'NotFound',component: () => import('./views/NotFound.vue')}]
})export default router

6.2 导航守卫

router.beforeEach((to, from, next) => {const isAuthenticated = localStorage.getItem('token')if (to.meta.requiresAuth && !isAuthenticated) {next('/login')} else {next()}
})

6.3 在组件中使用

<template><nav><RouterLink to="/">首页</RouterLink><RouterLink to="/about">关于</RouterLink></nav><RouterView v-slot="{ Component }"><Transition name="fade" mode="out-in"><component :is="Component" /></Transition></RouterView>
</template><script setup>
import { useRouter, useRoute } from 'vue-router'const router = useRouter()
const route = useRoute()// 编程式导航
function navigateToUser(userId) {router.push(`/user/${userId}`)
}// 访问当前路由参数
console.log(route.params.id)
</script>

第七章:实用工具与技巧

7.1 v-model 的高级用法

<!-- 基本用法 -->
<input v-model="text"><!-- 等价于 -->
<input :value="text" @input="text = $event.target.value"><!-- 自定义组件上使用v-model -->
<CustomInput v-model="searchText" /><!-- CustomInput.vue -->
<template><input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template><script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

7.2 组合式函数 (Composables)

创建可复用的逻辑:

// useCounter.js
import { ref, computed } from 'vue'export function useCounter(initialValue = 0) {const count = ref(initialValue)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}function decrement() {count.value--}return {count,doubleCount,increment,decrement}
}// 使用
import { useCounter } from './composables/useCounter'const { count, doubleCount, increment } = useCounter(10)

7.3 API 请求封装

// useApi.js
import { ref } from 'vue'
import axios from 'axios'export function useApi(url) {const data = ref(null)const error = ref(null)const loading = ref(true)const fetchData = async () => {loading.value = truetry {const response = await axios.get(url)data.value = response.data} catch (err) {error.value = err} finally {loading.value = false}}// 立即执行fetchData()return { data, error, loading, reload: fetchData }
}// 使用
const { data, error, loading } = useApi('https://api.example.com/users')

第八章:性能优化

8.1 避免不必要的渲染

  • 使用 computed 替代复杂的模板表达式
  • 拆分大型组件,使用 v-memo 避免不必要的重渲染
  • 合理使用 v-oncev-pre
<template><!-- 使用 v-memo 缓存列表项 --><div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">{{ item.name }}</div><!-- 使用 v-once 渲染一次后不再更新 --><div v-once><h2>静态内容</h2><p>{{ staticData }}</p></div>
</template>

8.2 异步组件与代码分割

import { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent({loader: () => import('./HeavyComponent.vue'),loadingComponent: LoadingComponent,errorComponent: ErrorComponent,delay: 200,timeout: 3000
})

8.3 虚拟列表处理大量数据

<template><VirtualList:items="largeDataset":item-height="50":visible-items="10"><template #default="{item}"><div class="item">{{ item.name }}</div></template></VirtualList>
</template>

第九章:TypeScript 与 Vue

9.1 组件类型定义

<script setup lang="ts">
import { ref, computed, PropType } from 'vue'interface User {id: numbername: stringemail: string
}const props = defineProps({user: {type: Object as PropType<User>,required: true},isActive: Boolean
})const emit = defineEmits<{(e: 'update', id: number): void(e: 'delete'): void
}>()const counter = ref<number>(0)
</script>

9.2 Composition API 与 TypeScript

// 组合式函数使用 TypeScript
export function usePagination<T>(items: T[], pageSize = 10) {const currentPage = ref(1)const totalPages = computed(() => Math.ceil(items.length / pageSize))const paginatedItems = computed(() => {const startIndex = (currentPage.value - 1) * pageSizereturn items.slice(startIndex, startIndex + pageSize)})return {currentPage,totalPages,paginatedItems,nextPage: () => {if (currentPage.value < totalPages.value) {currentPage.value++}},prevPage: () => {if (currentPage.value > 1) {currentPage.value--}}}
}

第十章:Vue3 生态系统与最佳实践

10.1 单元测试 (Vitest)

// counter.spec.js
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'describe('Counter.vue', () => {it('increments count when button is clicked', async () => {const wrapper = mount(Counter)expect(wrapper.text()).toContain('Count: 0')await wrapper.find('button').trigger('click')expect(wrapper.text()).toContain('Count: 1')})
})

10.2 服务器端渲染 (SSR) 与 Nuxt 3

Nuxt 3 是基于 Vue 3 的服务器端渲染框架:

npx nuxi init nuxt-app
cd nuxt-app
npm install
npm run dev

10.3 Vue 3 项目部署最佳实践

  • 使用 Vite 进行生产构建
  • 启用 gzip/brotli 压缩
  • 分割代码并使用 HTTP/2
  • 配置合理的缓存策略
# 使用 Vite 构建
npm run build# 预览生产构建
npm run preview

10.4 UI 组件库与设计系统

  • Element Plus
  • Naive UI
  • Vuetify
  • PrimeVue
// Element Plus 示例
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

第十一章:Vue3 集成 Ant Design Vue (2025版)

11.1 使用 yarn 安装 Ant Design Vue

Ant Design Vue 是蚂蚁金服基于 Ant Design 设计体系的 Vue 实现,适合企业级中后台产品。

# 创建项目
yarn create vite my-vue-app --template vue-ts# 进入项目目录
cd my-vue-app# 安装依赖
yarn# 安装 Ant Design Vue
yarn add ant-design-vue@4.x# 安装图标库
yarn add @ant-design/icons-vue

11.2 全局引入 Ant Design Vue

main.ts 中全局引入:

import { createApp } from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'const app = createApp(App)
app.use(Antd)
app.mount('#app')

11.3 按需引入 (推荐)

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'export default defineConfig({plugins: [vue(),Components({resolvers: [AntDesignVueResolver({importStyle: false, // css in js}),],}),],
})

然后在组件中直接使用:

<template><a-config-provider><a-space direction="vertical" style="width: 100%"><a-button type="primary" @click="showModal">打开弹窗</a-button><a-modal v-model:visible="visible" title="用户信息" @ok="handleOk"><a-form :model="formState" :rules="rules" layout="vertical"><a-form-item name="name" label="姓名"><a-input v-model:value="formState.name" /></a-form-item><a-form-item name="age" label="年龄"><a-input-number v-model:value="formState.age" /></a-form-item></a-form></a-modal><a-table :columns="columns" :data-source="dataSource" /></a-space></a-config-provider>
</template><script setup lang="ts">
import { ref, reactive } from 'vue'// 表单状态
const formState = reactive({name: '',age: 18
})// 表单规则
const rules = {name: [{ required: true, message: '请输入姓名' }],age: [{ required: true, message: '请输入年龄' }]
}// 弹窗控制
const visible = ref<boolean>(false)
const showModal = () => {visible.value = true
}
const handleOk = () => {console.log('表单提交:', formState)visible.value = false
}// 表格数据
const columns = [{ title: '姓名', dataIndex: 'name', key: 'name' },{ title: '年龄', dataIndex: 'age', key: 'age' },{ title: '地址', dataIndex: 'address', key: 'address' },
]
const dataSource = [{ key: '1', name: '张三', age: 32, address: '北京市朝阳区' },{ key: '2', name: '李四', age: 42, address: '上海市浦东新区' },
]
</script>

11.4 主题定制

使用 CSS 变量自定义主题 (2025年推荐方式):

<template><a-config-provider:theme="{token: {colorPrimary: '#1677ff',borderRadius: 6,},}"><app-content /></a-config-provider>
</template>

11.5 常用组件与最佳实践

数据展示
<!-- 表格 -->
<a-table:columns="columns" :data-source="dataSource":pagination="{ pageSize: 10 }" :loading="loading"@change="handleTableChange"
/><!-- 列表 -->
<a-list :data-source="listData"><template #renderItem="{ item }"><a-list-item><a-list-item-meta:title="item.title":description="item.description"><template #avatar><a-avatar :src="item.avatar" /></template></a-list-item-meta></a-list-item></template>
</a-list><!-- 卡片 -->
<a-card title="卡片标题" style="width: 300px"><template #extra><a href="#">更多</a></template><p>卡片内容</p>
</a-card>
表单处理
<template><a-form:model="formState"name="userForm":label-col="{ span: 6 }":wrapper-col="{ span: 18 }"@finish="onFinish"@finishFailed="onFinishFailed"><a-form-itemlabel="用户名"name="username":rules="[{ required: true, message: '请输入用户名!' }]"><a-input v-model:value="formState.username" /></a-form-item><a-form-itemlabel="密码"name="password":rules="[{ required: true, message: '请输入密码!' }]"><a-input-password v-model:value="formState.password" /></a-form-item><a-form-item name="remember" :wrapper-col="{ offset: 6, span: 18 }"><a-checkbox v-model:checked="formState.remember">记住我</a-checkbox></a-form-item><a-form-item :wrapper-col="{ offset: 6, span: 18 }"><a-button type="primary" html-type="submit">提交</a-button></a-form-item></a-form>
</template><script setup lang="ts">
import { reactive } from 'vue'interface FormState {username: string;password: string;remember: boolean;
}const formState = reactive<FormState>({username: '',password: '',remember: true,
})const onFinish = (values: FormState) => {console.log('Success:', values)
}const onFinishFailed = (errorInfo: any) => {console.log('Failed:', errorInfo)
}
</script>
布局组件
<template><a-layout><a-layout-header>Header</a-layout-header><a-layout><a-layout-sider>Sider</a-layout-sider><a-layout-content>Content</a-layout-content></a-layout><a-layout-footer>Footer</a-layout-footer></a-layout>
</template>

11.6 常见问题与解决方案

1. 样式冲突问题

Ant Design Vue 4.x 使用 CSS-in-JS,避免了全局样式污染,但如果需要覆盖默认样式:

<template><a-button class="custom-btn">按钮</a-button>
</template><style>
.custom-btn {border-radius: 0;
}
</style>
2. 性能优化
  • 使用按需加载减少包体积
  • 大数据表格考虑使用虚拟滚动
  • 对于频繁变化的数据采用不可变数据处理
3. 组件与Composition API集成
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { TableProps } from 'ant-design-vue'// 封装表格逻辑为可复用组件
function useTable<T>(fetchData: () => Promise<T[]>) {const dataSource = ref<T[]>([])const loading = ref<boolean>(false)const loadData = async () => {loading.value = truetry {dataSource.value = await fetchData()} finally {loading.value = false}}onMounted(() => {loadData()})const handleTableChange: TableProps['onChange'] = (pagination) => {console.log(pagination)// 可以在这里处理分页、筛选、排序等操作}return {dataSource,loading,loadData,handleTableChange}
}
</script>

11.7 其他常用组件库与Ant Design Vue的集成

Ant Design Vue 可以与其他库协同工作:

# 图表库
yarn add echarts @vue-chart/chart# 富文本编辑器
yarn add @wangeditor/editor @wangeditor/editor-for-vue# 拖拽排序
yarn add vuedraggable@next

第十二章:基于 Vue3 和 Ant Design Vue 的企业实战

12.1 项目搭建流程

完整的企业级项目目录结构:

my-project/
├── public/
├── src/
│   ├── api/              # API请求
│   ├── assets/           # 静态资源
│   ├── components/       # 公共组件
│   ├── composables/      # 组合式函数
│   ├── config/           # 全局配置
│   ├── directives/       # 自定义指令
│   ├── hooks/            # 自定义钩子
│   ├── layout/           # 布局组件
│   ├── router/           # 路由配置
│   ├── services/         # 服务层
│   ├── store/            # 状态管理
│   ├── utils/            # 工具函数
│   ├── views/            # 页面组件
│   ├── App.vue           # 根组件
│   └── main.ts           # 入口文件
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

创建指令:

# 创建项目
yarn create vite my-project --template vue-ts# 安装依赖
cd my-project
yarn# 安装必要库
yarn add ant-design-vue@4.x @ant-design/icons-vue
yarn add vue-router@4
yarn add pinia
yarn add axios
yarn add dayjs# 开发依赖
yarn add -D unplugin-vue-components unplugin-auto-import typescript

第十三章:Vue3 魔法语法大揭秘 🧙‍♂️

“如果你不理解Vue的语法,不是因为你不够聪明,而是因为没人用有趣的方式给你解释!” — 某Vue开发者的心声

13.1 模板语法: HTML上的魔法咒语 ✨

文本插值 - 双大括号,不是给大熊猫画眼圈
<template><div>{{ message }}</div><!-- 不是 {message} 也不是 ${message},是 Vue 独特的双括号! --><div>{{ 1 + 1 }}</div> <!-- 2 --><div>{{ message.split('').reverse().join('') }}</div> <!-- 字符串反转 --><div>{{ user.isVIP ? '尊贵的VIP用户' : '普通用户' }}</div>
</template><script setup>
import { ref } from 'vue'
const message = ref('Hello Vue')
const user = ref({ isVIP: true })
</script>
指令 - Vue版的"芝麻开门"
<template><!-- v-if: 元素界的薛定谔猫,又不是真的猫,是存在还是不存在取决于你的观察 --><div v-if="visible">现在你看到我了👀</div><button @click="visible = !visible">变变变,{{ visible ? '消失' : '出现' }}!</button><!-- v-for: 复制粘贴,但优雅得多 --><ul><li v-for="superhero in superheroes" :key="superhero.id">{{ superhero.name }} 的超能力是: {{ superhero.power }}</li></ul><!-- v-bind: 属性的变色龙,可以简写为冒号: --><img :src="imgUrl" :alt="`${user.name}的头像`"><!-- v-on: 事件魔术师,简写为@ --><button @click="sayHi">点我打招呼</button><input @keyup.enter="search"> <!-- 按回车搜索 --><!-- v-model: 双向绑定,数据界的灵魂伴侣 --><input v-model="username" placeholder="请输入用户名"><p>你好, {{ username || '神秘人' }}!</p>
</template><script setup>
import { ref } from 'vue'const visible = ref(true)
const username = ref('')
const imgUrl = ref('https://placekitten.com/200/200') // 谁不喜欢随机猫咪图呢?
const user = ref({ name: '熊猫人' })const superheroes = ref([{ id: 1, name: '钢铁侠', power: '超级智能和酷炫装甲' },{ id: 2, name: '蜘蛛侠', power: '被蜘蛛咬了之后什么都能粘' },{ id: 3, name: '绿巨人', power: '生气时变绿,裤子却神奇地没有完全破' }
])function sayHi() {alert(`你好啊,${username.value || '陌生人'}!今天过得怎么样?`)
}function search() {console.log(`正在搜索:${username.value}`)
}
</script>

13.2 Composition API:魔法药水配方 🧪

ref vs. reactive - 响应式的两种口味
<script setup>
import { ref, reactive } from 'vue'// ref - 给基本类型数据穿上响应式外衣
const count = ref(0)
console.log(count.value) // 记得加.value,不然就像忘穿裤子一样尴尬// reactive - 对象的响应式魔法
const userInfo = reactive({name: '张三',age: 25,hobbies: ['编程', '打游戏', '假装自己不是熬夜写代码']
})
console.log(userInfo.name) // 直接访问属性,没有.value,因为它已经是一个对象
</script>
为什么要用ref?(用搞笑的比喻解释)
// JavaScript的原始类型就像送外卖一样,是"值传递"
let price = 10
function discount(p) {p = p * 0.9 // 打九折
}
discount(price)
console.log(price) // 还是10,没变!因为我们改的是送货员手里的"复制品"// 而ref就像给每一个值包装了一个"包裹盒"
const price = ref(10)
function discount(p) {p.value = p.value * 0.9 // 打九折
}
discount(price)
console.log(price.value) // 9,变了!因为我们改的是"包裹"里的东西

13.3 配合搞笑例子的实用Hooks

用户登录状态追踪器 - “消失的饼干侦探”
// useAuth.js - 不再是枯燥的认证钩子
import { ref, onMounted } from 'vue'export function useAuth() {const user = ref(null)const isLoggedIn = ref(false)const isLoading = ref(true)function login(username, password) {isLoading.value = true// 假装在调用APIreturn new Promise((resolve) => {setTimeout(() => {if (username === 'admin' && password === 'password123') {user.value = {name: '超级管理员',avatar: '👨‍💼',powers: ['删库', '跑路', '甩锅']}isLoggedIn.value = truelocalStorage.setItem('token', 'super-secret-token-dont-steal')resolve({ success: true })} else {alert('登录失败! 提示: 试试 admin/password123 (太难猜了吧?)')resolve({ success: false })}isLoading.value = false}, 1000)})}function logout() {// 装作很认真地清理user.value = nullisLoggedIn.value = falselocalStorage.removeItem('token')console.log('已登出,您的购物车、心愿单和灵魂都已被清空')}// 检查是否已登录(饼干侦探上线)onMounted(() => {const token = localStorage.getItem('token')if (token) {// 假装验证tokenuser.value = {name: '超级管理员',avatar: '👨‍💼',lastLogin: '当你睡觉的时候'}isLoggedIn.value = trueconsole.log('饼干侦探发现您之前登录过!')}isLoading.value = false})return {user,isLoggedIn,isLoading,login,logout}
}
“天气控制器” - 带有趣预报的天气Hook
// useWeather.js
import { ref, computed } from 'vue'export function useWeather() {const temperature = ref(20)const conditions = ref('晴朗')const loading = ref(false)// 计算温度描述,带有幽默感const temperatureDescription = computed(() => {if (temperature.value < 0) return '冻得连北极熊都想搬家了🥶'if (temperature.value < 10) return '冷得让人怀疑人生的选择🧣'if (temperature.value < 20) return '有点凉,但不至于让你后悔出门🧥'if (temperature.value < 30) return '完美的温度,除非你是个企鹅🌞'if (temperature.value < 35) return '热得让人想抱住冰箱不放手🥵'return '欢迎来到地狱的温度体验,鸡蛋可在地上煎熟🔥'})// 获取天气的有趣函数async function fetchWeather(city) {loading.value = true// 模拟API调用return new Promise((resolve) => {setTimeout(() => {// 随机生成有趣的天气数据const weathers = ['晴朗', '多云', '小雨', '大雨', '雷暴', '龙卷风', '暴风雪', '外星人入侵']const temp = Math.floor(Math.random() * 40) - 5temperature.value = tempconditions.value = weathers[Math.floor(Math.random() * weathers.length)]loading.value = falseresolve({temperature: temp,conditions: conditions.value,forecast: '明天可能会更糟,或者更好,谁知道呢?天气预报员也在猜!'})}, 800)})}return {temperature,conditions,loading,temperatureDescription,fetchWeather}
}

13.4 有趣的实战案例:《猫咪点餐系统》🐱

这是一个结合了Vue3核心概念的迷你应用,一个猫咪专属的餐厅点餐系统:

<template><div class="cat-restaurant"><h1>🐱 喵星餐厅 <span v-if="auth.isLoggedIn">- 欢迎回来, {{ auth.user?.name }}</span></h1><!-- 登录部分 --><div v-if="!auth.isLoggedIn" class="login-section"><h2>请先登录再点餐</h2><a-form><a-input v-model:value="loginForm.username" placeholder="用户名 (提示: admin)" /><a-input-password v-model:value="loginForm.password" placeholder="密码 (提示: password123)" /><a-button type="primary" @click="handleLogin" :loading="auth.isLoading">喵星人身份验证</a-button></a-form></div><!-- 点餐部分 --><div v-else><a-row :gutter="16"><a-col :span="16"><h2>今日菜单 <a-tag color="red">猫咪专属</a-tag></h2><a-list :data-source="menu" class="menu-list"><template #renderItem="{ item }"><a-list-item><a-card hoverable style="width: 100%"><template #title>{{ item.name }} <a-tag color="green" v-if="item.popular">爆款</a-tag></template><template #cover><div class="food-image">{{ item.emoji }}</div></template><template #extra><a-button type="primary" @click="addToCart(item)">添加 ({{ item.price }}猫币)</a-button></template><p>{{ item.description }}</p></a-card></a-list-item></template></a-list></a-col><a-col :span="8"><a-affix :offset-top="20"><a-card title="购物车🛒" class="cart"><template #extra><a-button danger @click="cart = []">清空</a-button></template><a-empty v-if="cart.length === 0" description="购物车空空如也,喵~" /><div v-else><div v-for="(item, index) in cart" :key="index" class="cart-item"><span>{{ item.emoji }} {{ item.name }}</span><span>{{ item.price }}猫币 <a-button type="text" danger @click="removeFromCart(index)">❌</a-button></span></div><a-divider /><div class="cart-total"><span>总计:</span><span>{{ totalPrice }}猫币</span></div><a-button type="primary" block @click="placeOrder">确认订单并支付</a-button></div></a-card></a-affix></a-col></a-row></div></div>
</template><script setup>
import { reactive, ref, computed } from 'vue'
import { useAuth } from './composables/useAuth'
import { message } from 'ant-design-vue'// 使用我们的认证Hook
const auth = useAuth()// 登录表单
const loginForm = reactive({username: '',password: ''
})// 处理登录
const handleLogin = async () => {const result = await auth.login(loginForm.username, loginForm.password)if (result.success) {message.success('登录成功!猫粮即将送达~')}
}// 菜单数据
const menu = ref([{ id: 1,name: '豪华金枪鱼大餐',emoji: '🐟',description: '新鲜金枪鱼切片,撒上猫薄荷,保证让您家主子舔碗舔到见底',price: 25,popular: true},{ id: 2,name: '鸡肉慕斯',emoji: '🍗',description: '细嫩鸡胸肉打成泥状,质地如慕斯般丝滑,好吃到喵星人打滚',price: 18,popular: false},{ id: 3,name: '三文鱼骨头汤',emoji: '🥣',description: '熬制8小时的三文鱼骨头汤,富含胶原蛋白,让猫主子毛发亮丽',price: 15,popular: false},{ id: 4,name: '皇家猫草沙拉',emoji: '🥬',description: '有机猫草配上少量牛油果,是减肥猫咪的健康选择',price: 12,popular: false},{ id: 5,name: '老鼠形状小饼干',emoji: '🐭',description: '外形酷似老鼠的猫饼干,让您的猫在吃零食的同时体验捕猎快感',price: 8,popular: true}
])// 购物车
const cart = ref([])// 添加到购物车
const addToCart = (item) => {cart.value.push({ ...item })message.success(`${item.name}已添加到购物车`)
}// 从购物车移除
const removeFromCart = (index) => {cart.value.splice(index, 1)
}// 计算总价
const totalPrice = computed(() => {return cart.value.reduce((total, item) => total + item.price, 0)
})// 下单
const placeOrder = () => {message.loading('正在处理您的订单...')setTimeout(() => {message.success('下单成功!您的猫咪美食即将送达,约15分钟后到达猫窝')cart.value = []}, 2000)
}
</script><style scoped>
.cat-restaurant {max-width: 1200px;margin: 0 auto;padding: 20px;
}.login-section {max-width: 400px;margin: 40px auto;text-align: center;
}.menu-list {margin-top: 20px;
}.food-image {height: 120px;display: flex;justify-content: center;align-items: center;background: #fafafa;font-size: 60px;
}.cart {margin-top: 20px;
}.cart-item {display: flex;justify-content: space-between;margin-bottom: 10px;
}.cart-total {display: flex;justify-content: space-between;font-weight: bold;margin-bottom: 15px;
}
</style>

13.5 你与Vue开发者的日常对话

初级开发者: "为什么我的数据不更新啊?"
高级开发者: "你是不是忘了用.value?"
初级开发者: "哦对..."初级开发者: "为什么按钮点击没反应?"
高级开发者: "你用的是@click还是v-on:click?"
初级开发者: "我用的onClick..."
高级开发者: "这是React...不是Vue..."产品经理: "这个功能不难吧,下午能上线吗?"
Vue开发者: "嗯,简单,借我一台量子计算机,下午给你。"面试官: "说说你对Vue生命周期的理解?"
聪明的应聘者: "就像人的一生:出生(setup),长牙(onBeforeMount),上学(onMounted),工作(onBeforeUpdate, onUpdated),退休(onBeforeUnmount),去世(onUnmounted),唯一不同的是Vue组件可以投胎转世(keep-alive激活)。"

第十四章:Vue3 纯语法讲解

14.1 模板语法

Vue3 的模板语法是基于 HTML 的扩展,支持动态绑定数据和事件。

文本插值
<template><p>{{ message }}</p>
</template><script setup>
import { ref } from 'vue'
const message = ref('Hello, Vue3!')
</script>
属性绑定
<template><img :src="imageUrl" :alt="altText" />
</template><script setup>
const imageUrl = 'https://example.com/image.png'
const altText = '示例图片'
</script>
条件渲染
<template><p v-if="isLoggedIn">欢迎回来!</p><p v-else>请登录。</p>
</template><script setup>
const isLoggedIn = false
</script>
列表渲染
<template><ul><li v-for="item in items" :key="item.id">{{ item.name }}</li></ul>
</template><script setup>
const items = [{ id: 1, name: '苹果' },{ id: 2, name: '香蕉' },{ id: 3, name: '橙子' }
]
</script>

14.2 指令

Vue 提供了一组内置指令,用于操作 DOM。

v-bind
<template><a :href="url">点击这里</a>
</template><script setup>
const url = 'https://vuejs.org'
</script>
v-on
<template><button @click="handleClick">点击我</button>
</template><script setup>
function handleClick() {alert('按钮被点击了!')
}
</script>
v-model
<template><input v-model="username" placeholder="请输入用户名" /><p>你好,{{ username }}</p>
</template><script setup>
import { ref } from 'vue'
const username = ref('')
</script>

14.3 响应式系统

Vue3 的响应式系统基于 Proxy,提供了更强大的功能。

ref
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {count.value++
}
</script>
reactive
<script setup>
import { reactive } from 'vue'
const state = reactive({name: '张三',age: 25
})
</script>
computed
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
</script>
watch
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})
</script>

14.4 生命周期

Vue3 提供了更灵活的生命周期钩子。

常用生命周期
<script setup>
import { onMounted, onUnmounted } from 'vue'onMounted(() => {console.log('组件已挂载')
})onUnmounted(() => {console.log('组件已卸载')
})
</script>

14.5 Composition API

Composition API 是 Vue3 的核心特性,提供了更灵活的逻辑复用方式。

setup
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {count.value++
}
</script>
自定义组合式函数
// useCounter.js
import { ref } from 'vue'
export function useCounter() {const count = ref(0)function increment() {count.value++}return { count, increment }
}
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter()
</script>

14.6 模板引用

<template><div ref="box">我是一个盒子</div>
</template><script setup>
import { ref, onMounted } from 'vue'
const box = ref(null)
onMounted(() => {console.log(box.value) // 访问 DOM 元素
})
</script>

14.7 动态组件

<template><component :is="currentComponent"></component>
</template><script setup>
import { ref } from 'vue'
const currentComponent = ref('MyComponent')
</script>

14.8 插槽

<template><BaseLayout><template #header>这是头部</template><template #default>这是内容</template><template #footer>这是底部</template></BaseLayout>
</template><script>
export default {components: {BaseLayout: {template: `<div><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer></div>`}}
}
</script>

第八章:setup 语法糖详解

8.1 setup 语法糖基础

<script setup> 是 Vue3 中组合式 API 的编译时语法糖,它简化了组件的编写方式。

8.1.1 基本用法
<template><div><h1>{{ title }}</h1><button @click="increment">点击次数: {{ count }}</button></div>
</template><script setup>
import { ref } from 'vue'// 响应式状态
const title = ref('Hello Setup!')
const count = ref(0)// 方法
function increment() {count.value++
}
</script>

🔑 关键点

  • <script setup> 中声明的变量和函数自动暴露给模板
  • 不需要 return 语句
  • 组件自动注册,无需手动注册
8.1.2 组件自动注册
<template><div><ChildComponent /><BaseButton>点击我</BaseButton></div>
</template><script setup>
// 导入的组件自动注册,无需手动注册
import ChildComponent from './ChildComponent.vue'
import BaseButton from './components/BaseButton.vue'
</script>

8.2 响应式数据声明

8.2.1 ref 的使用
<script setup>
import { ref } from 'vue'// 基本类型
const count = ref(0)
const name = ref('张三')// 对象类型
const user = ref({name: '李四',age: 25
})// 数组类型
const items = ref(['苹果', '香蕉', '橙子'])// 在 JS 中访问和修改
console.log(count.value) // 0
count.value++console.log(user.value.name) // 李四
user.value.age = 26// 在模板中直接使用,无需 .value
</script><template><div><p>计数: {{ count }}</p><p>姓名: {{ name }}</p><p>用户: {{ user.name }}, {{ user.age }}岁</p><ul><li v-for="item in items" :key="item">{{ item }}</li></ul></div>
</template>
8.2.2 reactive 的使用
<script setup>
import { reactive } from 'vue'// 对象类型
const state = reactive({count: 0,user: {name: '张三',age: 25},items: ['苹果', '香蕉', '橙子']
})// 直接访问和修改属性,无需 .value
console.log(state.count) // 0
state.count++console.log(state.user.name) // 张三
state.user.age = 26// 数组操作
state.items.push('葡萄')
</script><template><div><p>计数: {{ state.count }}</p><p>用户: {{ state.user.name }}, {{ state.user.age }}岁</p><ul><li v-for="item in state.items" :key="item">{{ item }}</li></ul></div>
</template>

8.3 计算属性和侦听器

8.3.1 computed 的使用
<script setup>
import { ref, computed } from 'vue'const count = ref(0)
const name = ref('张三')// 只读计算属性
const doubleCount = computed(() => count.value * 2)// 可写计算属性
const fullName = computed({get() {return name.value},set(newValue) {name.value = newValue}
})
</script><template><div><p>原始值: {{ count }}</p><p>双倍值: {{ doubleCount }}</p><input v-model="fullName" /></div>
</template>
8.3.2 watch 和 watchEffect 的使用
<script setup>
import { ref, watch, watchEffect } from 'vue'const count = ref(0)
const name = ref('张三')// watch 监听单个数据源
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})// watch 监听多个数据源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {console.log(`count: ${oldCount} -> ${newCount}`)console.log(`name: ${oldName} -> ${newName}`)
})// watchEffect 自动收集依赖
watchEffect(() => {console.log(`当前状态: count=${count.value}, name=${name.value}`)
})
</script>

8.4 生命周期钩子

<script setup>
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
} from 'vue'// 组件挂载前
onBeforeMount(() => {console.log('组件挂载前')
})// 组件挂载后
onMounted(() => {console.log('组件已挂载')// 适合进行数据获取、DOM 操作等
})// 组件更新前
onBeforeUpdate(() => {console.log('组件更新前')
})// 组件更新后
onUpdated(() => {console.log('组件已更新')
})// 组件卸载前
onBeforeUnmount(() => {console.log('组件卸载前')// 适合清理定时器、事件监听器等
})// 组件卸载后
onUnmounted(() => {console.log('组件已卸载')
})
</script>

8.5 Props 和 Emits

8.5.1 Props 定义
<script setup>
// 方式 1: 运行时声明
const props = defineProps({title: String,count: {type: Number,default: 0},items: {type: Array,required: true}
})// 方式 2: TypeScript 类型声明
// const props = defineProps<{
//   title: string
//   count?: number
//   items: string[]
// }>()// 方式 3: 带默认值的 TypeScript 类型声明
// const props = withDefaults(defineProps<{
//   title: string
//   count?: number
//   items?: string[]
// }>(), {
//   count: 0,
//   items: () => []
// })
</script><template><div><h1>{{ title }}</h1><p>计数: {{ count }}</p><ul><li v-for="item in items" :key="item">{{ item }}</li></ul></div>
</template>
8.5.2 Emits 定义
<script setup>
// 方式 1: 运行时声明
const emit = defineEmits(['update', 'delete'])// 方式 2: TypeScript 类型声明
// const emit = defineEmits<{
//   (e: 'update', id: number): void
//   (e: 'delete', id: number): void
// }>()function handleUpdate(id) {emit('update', id)
}function handleDelete(id) {emit('delete', id)
}
</script><template><div><button @click="handleUpdate(1)">更新</button><button @click="handleDelete(1)">删除</button></div>
</template>

8.6 实际应用示例

8.6.1 表单处理
<script setup>
import { ref, reactive } from 'vue'// 表单数据
const formData = reactive({username: '',email: '',password: ''
})// 表单验证
const errors = reactive({username: '',email: '',password: ''
})// 提交状态
const isSubmitting = ref(false)// 表单验证函数
function validateForm() {let isValid = trueif (!formData.username) {errors.username = '用户名不能为空'isValid = false}if (!formData.email) {errors.email = '邮箱不能为空'isValid = false} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {errors.email = '邮箱格式不正确'isValid = false}if (!formData.password) {errors.password = '密码不能为空'isValid = false} else if (formData.password.length < 6) {errors.password = '密码长度不能小于6位'isValid = false}return isValid
}// 提交表单
async function handleSubmit() {if (!validateForm()) returnisSubmitting.value = truetry {// 模拟 API 调用await new Promise(resolve => setTimeout(resolve, 1000))console.log('表单提交成功:', formData)// 重置表单Object.keys(formData).forEach(key => {formData[key] = ''})} catch (error) {console.error('提交失败:', error)} finally {isSubmitting.value = false}
}
</script><template><form @submit.prevent="handleSubmit"><div><label>用户名:</label><input v-model="formData.username" /><span class="error">{{ errors.username }}</span></div><div><label>邮箱:</label><input v-model="formData.email" type="email" /><span class="error">{{ errors.email }}</span></div><div><label>密码:</label><input v-model="formData.password" type="password" /><span class="error">{{ errors.password }}</span></div><button type="submit" :disabled="isSubmitting">{{ isSubmitting ? '提交中...' : '提交' }}</button></form>
</template><style scoped>
.error {color: red;font-size: 0.8em;margin-left: 5px;
}
</style>
8.6.2 数据获取和状态管理
<script setup>
import { ref, onMounted } from 'vue'// 状态
const posts = ref([])
const loading = ref(false)
const error = ref(null)// 获取数据
async function fetchPosts() {loading.value = trueerror.value = nulltry {const response = await fetch('https://api.example.com/posts')const data = await response.json()posts.value = data} catch (e) {error.value = '获取数据失败: ' + e.message} finally {loading.value = false}
}// 组件挂载时获取数据
onMounted(fetchPosts)
</script><template><div><h1>文章列表</h1><!-- 加载状态 --><div v-if="loading" class="loading">加载中...</div><!-- 错误状态 --><div v-else-if="error" class="error">{{ error }}</div><!-- 数据展示 --><div v-else><div v-for="post in posts" :key="post.id" class="post"><h2>{{ post.title }}</h2><p>{{ post.content }}</p></div></div></div>
</template><style scoped>
.loading {text-align: center;padding: 20px;
}.error {color: red;padding: 20px;
}.post {margin-bottom: 20px;padding: 15px;border: 1px solid #ddd;border-radius: 4px;
}
</style>

8.7 最佳实践

  1. 使用 TypeScript

    • 为 props 和 emits 添加类型声明
    • 使用类型注解提高代码可维护性
  2. 组件拆分

    • 将复杂组件拆分为更小的组件
    • 使用组合式函数抽取可复用逻辑
  3. 性能优化

    • 使用 shallowRefshallowReactive 处理大型数据结构
    • 合理使用 computed 缓存计算结果
  4. 代码组织

    • 相关逻辑放在一起
    • 使用注释分隔不同的功能块
    • 提取可复用的逻辑到组合式函数
  5. 错误处理

    • 使用 try-catch 处理异步操作
    • 提供友好的错误提示
    • 实现错误恢复机制

第九章:Vue3 高级组件模式

9.1 组件通信模式

9.1.1 事件总线模式
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import mitt from 'mitt'// 创建事件总线
const emitter = mitt()// 组件状态
const message = ref('')// 监听事件
function handleMessage(msg) {message.value = msg
}onMounted(() => {emitter.on('message', handleMessage)
})onUnmounted(() => {emitter.off('message', handleMessage)
})// 发送事件
function sendMessage() {emitter.emit('message', 'Hello from Component A!')
}
</script><template><div><button @click="sendMessage">发送消息</button><p>收到的消息: {{ message }}</p></div>
</template>
9.1.2 依赖注入模式
<script setup>
import { provide, inject, ref } from 'vue'// 在父组件中提供数据
const theme = ref('light')
const toggleTheme = () => {theme.value = theme.value === 'light' ? 'dark' : 'light'
}provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script><template><div :class="theme"><slot></slot></div>
</template><!-- 子组件 -->
<script setup>
import { inject } from 'vue'const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script><template><button @click="toggleTheme">当前主题: {{ theme }}</button>
</template>

9.2 高级组件模式

9.2.1 高阶组件模式
<script setup>
import { ref, onMounted } from 'vue'// 高阶组件函数
function withLoading(WrappedComponent) {return {setup(props) {const loading = ref(true)const data = ref(null)const error = ref(null)onMounted(async () => {try {// 模拟数据加载await new Promise(resolve => setTimeout(resolve, 1000))data.value = { message: '数据加载成功' }} catch (e) {error.value = e} finally {loading.value = false}})return () => {if (loading.value) {return <div>加载中...</div>}if (error.value) {return <div>错误: {error.value.message}</div>}return <WrappedComponent data={data.value} {...props} />}}}
}// 使用高阶组件
const EnhancedComponent = withLoading({setup(props) {return () => <div>{props.data.message}</div>}
})
</script>
9.2.2 组合式函数模式
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'// 鼠标位置跟踪组合式函数
function useMousePosition() {const x = ref(0)const y = ref(0)function updatePosition(event) {x.value = event.pageXy.value = event.pageY}onMounted(() => {window.addEventListener('mousemove', updatePosition)})onUnmounted(() => {window.removeEventListener('mousemove', updatePosition)})return { x, y }
}// 使用组合式函数
const { x, y } = useMousePosition()
</script><template><div>鼠标位置: ({{ x }}, {{ y }})</div>
</template>

9.3 性能优化模式

9.3.1 虚拟滚动
<script setup>
import { ref, computed } from 'vue'// 虚拟滚动组件
const items = ref(Array.from({ length: 10000 }, (_, i) => ({id: i,text: `Item ${i}`
})))const itemHeight = 50
const visibleCount = 10
const scrollTop = ref(0)const visibleItems = computed(() => {const start = Math.floor(scrollTop.value / itemHeight)const end = start + visibleCountreturn items.value.slice(start, end)
})const totalHeight = computed(() => items.value.length * itemHeight)function handleScroll(event) {scrollTop.value = event.target.scrollTop
}
</script><template><div class="virtual-scroll"@scroll="handleScroll":style="{ height: `${visibleCount * itemHeight}px` }"><div class="virtual-scroll-content":style="{ height: `${totalHeight}px` }"><divv-for="item in visibleItems":key="item.id"class="virtual-scroll-item":style="{ height: `${itemHeight}px`,transform: `translateY(${item.id * itemHeight}px)`}">{{ item.text }}</div></div></div>
</template><style scoped>
.virtual-scroll {overflow-y: auto;border: 1px solid #ddd;
}.virtual-scroll-content {position: relative;
}.virtual-scroll-item {position: absolute;width: 100%;padding: 10px;box-sizing: border-box;border-bottom: 1px solid #eee;
}
</style>
9.3.2 懒加载组件
<script setup>
import { defineAsyncComponent } from 'vue'// 懒加载组件
const AsyncComponent = defineAsyncComponent({loader: () => import('./HeavyComponent.vue'),loadingComponent: {setup() {return () => <div>加载中...</div>}},errorComponent: {setup() {return () => <div>加载失败</div>}},delay: 200,timeout: 3000
})
</script><template><Suspense><template #default><AsyncComponent /></template><template #fallback><div>加载中...</div></template></Suspense>
</template>

9.4 状态管理模式

9.4.1 简单的状态管理
<script setup>
import { reactive, readonly } from 'vue'// 创建状态存储
const state = reactive({count: 0,user: null
})// 创建只读状态
const readonlyState = readonly(state)// 创建 actions
const actions = {increment() {state.count++},setUser(user) {state.user = user}
}// 导出状态和 actions
defineExpose({state: readonlyState,actions
})
</script><!-- 使用状态管理 -->
<script setup>
import { inject } from 'vue'const store = inject('store')
</script><template><div><p>Count: {{ store.state.count }}</p><button @click="store.actions.increment">增加</button></div>
</template>
9.4.2 组合式状态管理
<script setup>
import { ref, computed } from 'vue'// 创建状态管理组合式函数
function useCounter() {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}function decrement() {count.value--}return {count,doubleCount,increment,decrement}
}// 使用状态管理
const { count, doubleCount, increment, decrement } = useCounter()
</script><template><div><p>Count: {{ count }}</p><p>Double: {{ doubleCount }}</p><button @click="increment">+</button><button @click="decrement">-</button></div>
</template>

第十章:Vue3 生态系统与最佳实践

10.1 单元测试 (Vitest)

// counter.spec.js
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'describe('Counter.vue', () => {it('increments count when button is clicked', async () => {const wrapper = mount(Counter)expect(wrapper.text()).toContain('Count: 0')await wrapper.find('button').trigger('click')expect(wrapper.text()).toContain('Count: 1')})
})

10.2 服务器端渲染 (SSR) 与 Nuxt 3

Nuxt 3 是基于 Vue 3 的服务器端渲染框架:

npx nuxi init nuxt-app
cd nuxt-app
npm install
npm run dev

10.3 Vue 3 项目部署最佳实践

  • 使用 Vite 进行生产构建
  • 启用 gzip/brotli 压缩
  • 分割代码并使用 HTTP/2
  • 配置合理的缓存策略
# 使用 Vite 构建
npm run build# 预览生产构建
npm run preview

10.4 UI 组件库与设计系统

  • Element Plus
  • Naive UI
  • Vuetify
  • PrimeVue
// Element Plus 示例
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

第十一章:Vue3 集成 Ant Design Vue (2025版)

11.1 使用 yarn 安装 Ant Design Vue

Ant Design Vue 是蚂蚁金服基于 Ant Design 设计体系的 Vue 实现,适合企业级中后台产品。

# 创建项目
yarn create vite my-vue-app --template vue-ts# 进入项目目录
cd my-vue-app# 安装依赖
yarn# 安装 Ant Design Vue
yarn add ant-design-vue@4.x# 安装图标库
yarn add @ant-design/icons-vue

11.2 全局引入 Ant Design Vue

main.ts 中全局引入:

import { createApp } from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'const app = createApp(App)
app.use(Antd)
app.mount('#app')

11.3 按需引入 (推荐)

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'export default defineConfig({plugins: [vue(),Components({resolvers: [AntDesignVueResolver({importStyle: false, // css in js}),],}),],
})

然后在组件中直接使用:

<template><a-config-provider><a-space direction="vertical" style="width: 100%"><a-button type="primary" @click="showModal">打开弹窗</a-button><a-modal v-model:visible="visible" title="用户信息" @ok="handleOk"><a-form :model="formState" :rules="rules" layout="vertical"><a-form-item name="name" label="姓名"><a-input v-model:value="formState.name" /></a-form-item><a-form-item name="age" label="年龄"><a-input-number v-model:value="formState.age" /></a-form-item></a-form></a-modal><a-table :columns="columns" :data-source="dataSource" /></a-space></a-config-provider>
</template><script setup lang="ts">
import { ref, reactive } from 'vue'// 表单状态
const formState = reactive({name: '',age: 18
})// 表单规则
const rules = {name: [{ required: true, message: '请输入姓名' }],age: [{ required: true, message: '请输入年龄' }]
}// 弹窗控制
const visible = ref<boolean>(false)
const showModal = () => {visible.value = true
}
const handleOk = () => {console.log('表单提交:', formState)visible.value = false
}// 表格数据
const columns = [{ title: '姓名', dataIndex: 'name', key: 'name' },{ title: '年龄', dataIndex: 'age', key: 'age' },{ title: '地址', dataIndex: 'address', key: 'address' },
]
const dataSource = [{ key: '1', name: '张三', age: 32, address: '北京市朝阳区' },{ key: '2', name: '李四', age: 42, address: '上海市浦东新区' },
]
</script>

11.4 主题定制

使用 CSS 变量自定义主题 (2025年推荐方式):

<template><a-config-provider:theme="{token: {colorPrimary: '#1677ff',borderRadius: 6,},}"><app-content /></a-config-provider>
</template>

11.5 常用组件与最佳实践

数据展示
<!-- 表格 -->
<a-table:columns="columns" :data-source="dataSource":pagination="{ pageSize: 10 }" :loading="loading"@change="handleTableChange"
/><!-- 列表 -->
<a-list :data-source="listData"><template #renderItem="{ item }"><a-list-item><a-list-item-meta:title="item.title":description="item.description"><template #avatar><a-avatar :src="item.avatar" /></template></a-list-item-meta></a-list-item></template>
</a-list><!-- 卡片 -->
<a-card title="卡片标题" style="width: 300px"><template #extra><a href="#">更多</a></template><p>卡片内容</p>
</a-card>
表单处理
<template><a-form:model="formState"name="userForm":label-col="{ span: 6 }":wrapper-col="{ span: 18 }"@finish="onFinish"@finishFailed="onFinishFailed"><a-form-itemlabel="用户名"name="username":rules="[{ required: true, message: '请输入用户名!' }]"><a-input v-model:value="formState.username" /></a-form-item><a-form-itemlabel="密码"name="password":rules="[{ required: true, message: '请输入密码!' }]"><a-input-password v-model:value="formState.password" /></a-form-item><a-form-item name="remember" :wrapper-col="{ offset: 6, span: 18 }"><a-checkbox v-model:checked="formState.remember">记住我</a-checkbox></a-form-item><a-form-item :wrapper-col="{ offset: 6, span: 18 }"><a-button type="primary" html-type="submit">提交</a-button></a-form-item></a-form>
</template><script setup lang="ts">
import { reactive } from 'vue'interface FormState {username: string;password: string;remember: boolean;
}const formState = reactive<FormState>({username: '',password: '',remember: true,
})const onFinish = (values: FormState) => {console.log('Success:', values)
}const onFinishFailed = (errorInfo: any) => {console.log('Failed:', errorInfo)
}
</script>
布局组件
<template><a-layout><a-layout-header>Header</a-layout-header><a-layout><a-layout-sider>Sider</a-layout-sider><a-layout-content>Content</a-layout-content></a-layout><a-layout-footer>Footer</a-layout-footer></a-layout>
</template>

11.6 常见问题与解决方案

1. 样式冲突问题

Ant Design Vue 4.x 使用 CSS-in-JS,避免了全局样式污染,但如果需要覆盖默认样式:

<template><a-button class="custom-btn">按钮</a-button>
</template><style>
.custom-btn {border-radius: 0;
}
</style>
2. 性能优化
  • 使用按需加载减少包体积
  • 大数据表格考虑使用虚拟滚动
  • 对于频繁变化的数据采用不可变数据处理
3. 组件与Composition API集成
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { TableProps } from 'ant-design-vue'// 封装表格逻辑为可复用组件
function useTable<T>(fetchData: () => Promise<T[]>) {const dataSource = ref<T[]>([])const loading = ref<boolean>(false)const loadData = async () => {loading.value = truetry {dataSource.value = await fetchData()} finally {loading.value = false}}onMounted(() => {loadData()})const handleTableChange: TableProps['onChange'] = (pagination) => {console.log(pagination)// 可以在这里处理分页、筛选、排序等操作}return {dataSource,loading,loadData,handleTableChange}
}
</script>

11.7 其他常用组件库与Ant Design Vue的集成

Ant Design Vue 可以与其他库协同工作:

# 图表库
yarn add echarts @vue-chart/chart# 富文本编辑器
yarn add @wangeditor/editor @wangeditor/editor-for-vue# 拖拽排序
yarn add vuedraggable@next

第十二章:基于 Vue3 和 Ant Design Vue 的企业实战

12.1 项目搭建流程

完整的企业级项目目录结构:

my-project/
├── public/
├── src/
│   ├── api/              # API请求
│   ├── assets/           # 静态资源
│   ├── components/       # 公共组件
│   ├── composables/      # 组合式函数
│   ├── config/           # 全局配置
│   ├── directives/       # 自定义指令
│   ├── hooks/            # 自定义钩子
│   ├── layout/           # 布局组件
│   ├── router/           # 路由配置
│   ├── services/         # 服务层
│   ├── store/            # 状态管理
│   ├── utils/            # 工具函数
│   ├── views/            # 页面组件
│   ├── App.vue           # 根组件
│   └── main.ts           # 入口文件
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

创建指令:

# 创建项目
yarn create vite my-project --template vue-ts# 安装依赖
cd my-project
yarn# 安装必要库
yarn add ant-design-vue@4.x @ant-design/icons-vue
yarn add vue-router@4
yarn add pinia
yarn add axios
yarn add dayjs# 开发依赖
yarn add -D unplugin-vue-components unplugin-auto-import typescript

第十三章:Vue3 魔法语法大揭秘 🧙‍♂️

“如果你不理解Vue的语法,不是因为你不够聪明,而是因为没人用有趣的方式给你解释!” — 某Vue开发者的心声

13.1 模板语法: HTML上的魔法咒语 ✨

文本插值 - 双大括号,不是给大熊猫画眼圈
<template><div>{{ message }}</div><!-- 不是 {message} 也不是 ${message},是 Vue 独特的双括号! --><div>{{ 1 + 1 }}</div> <!-- 2 --><div>{{ message.split('').reverse().join('') }}</div> <!-- 字符串反转 --><div>{{ user.isVIP ? '尊贵的VIP用户' : '普通用户' }}</div>
</template><script setup>
import { ref } from 'vue'
const message = ref('Hello Vue')
const user = ref({ isVIP: true })
</script>
指令 - Vue版的"芝麻开门"
<template><!-- v-if: 元素界的薛定谔猫,又不是真的猫,是存在还是不存在取决于你的观察 --><div v-if="visible">现在你看到我了👀</div><button @click="visible = !visible">变变变,{{ visible ? '消失' : '出现' }}!</button><!-- v-for: 复制粘贴,但优雅得多 --><ul><li v-for="superhero in superheroes" :key="superhero.id">{{ superhero.name }} 的超能力是: {{ superhero.power }}</li></ul><!-- v-bind: 属性的变色龙,可以简写为冒号: --><img :src="imgUrl" :alt="`${user.name}的头像`"><!-- v-on: 事件魔术师,简写为@ --><button @click="sayHi">点我打招呼</button><input @keyup.enter="search"> <!-- 按回车搜索 --><!-- v-model: 双向绑定,数据界的灵魂伴侣 --><input v-model="username" placeholder="请输入用户名"><p>你好, {{ username || '神秘人' }}!</p>
</template><script setup>
import { ref } from 'vue'const visible = ref(true)
const username = ref('')
const imgUrl = ref('https://placekitten.com/200/200') // 谁不喜欢随机猫咪图呢?
const user = ref({ name: '熊猫人' })const superheroes = ref([{ id: 1, name: '钢铁侠', power: '超级智能和酷炫装甲' },{ id: 2, name: '蜘蛛侠', power: '被蜘蛛咬了之后什么都能粘' },{ id: 3, name: '绿巨人', power: '生气时变绿,裤子却神奇地没有完全破' }
])function sayHi() {alert(`你好啊,${username.value || '陌生人'}!今天过得怎么样?`)
}function search() {console.log(`正在搜索:${username.value}`)
}
</script>

13.2 Composition API:魔法药水配方 🧪

ref vs. reactive - 响应式的两种口味
<script setup>
import { ref, reactive } from 'vue'// ref - 给基本类型数据穿上响应式外衣
const count = ref(0)
console.log(count.value) // 记得加.value,不然就像忘穿裤子一样尴尬// reactive - 对象的响应式魔法
const userInfo = reactive({name: '张三',age: 25,hobbies: ['编程', '打游戏', '假装自己不是熬夜写代码']
})
console.log(userInfo.name) // 直接访问属性,没有.value,因为它已经是一个对象
</script>
为什么要用ref?(用搞笑的比喻解释)
// JavaScript的原始类型就像送外卖一样,是"值传递"
let price = 10
function discount(p) {p = p * 0.9 // 打九折
}
discount(price)
console.log(price) // 还是10,没变!因为我们改的是送货员手里的"复制品"// 而ref就像给每一个值包装了一个"包裹盒"
const price = ref(10)
function discount(p) {p.value = p.value * 0.9 // 打九折
}
discount(price)
console.log(price.value) // 9,变了!因为我们改的是"包裹"里的东西

13.3 配合搞笑例子的实用Hooks

用户登录状态追踪器 - “消失的饼干侦探”
// useAuth.js - 不再是枯燥的认证钩子
import { ref, onMounted } from 'vue'export function useAuth() {const user = ref(null)const isLoggedIn = ref(false)const isLoading = ref(true)function login(username, password) {isLoading.value = true// 假装在调用APIreturn new Promise((resolve) => {setTimeout(() => {if (username === 'admin' && password === 'password123') {user.value = {name: '超级管理员',avatar: '👨‍💼',powers: ['删库', '跑路', '甩锅']}isLoggedIn.value = truelocalStorage.setItem('token', 'super-secret-token-dont-steal')resolve({ success: true })} else {alert('登录失败! 提示: 试试 admin/password123 (太难猜了吧?)')resolve({ success: false })}isLoading.value = false}, 1000)})}function logout() {// 装作很认真地清理user.value = nullisLoggedIn.value = falselocalStorage.removeItem('token')console.log('已登出,您的购物车、心愿单和灵魂都已被清空')}// 检查是否已登录(饼干侦探上线)onMounted(() => {const token = localStorage.getItem('token')if (token) {// 假装验证tokenuser.value = {name: '超级管理员',avatar: '👨‍💼',lastLogin: '当你睡觉的时候'}isLoggedIn.value = trueconsole.log('饼干侦探发现您之前登录过!')}isLoading.value = false})return {user,isLoggedIn,isLoading,login,logout}
}
“天气控制器” - 带有趣预报的天气Hook
// useWeather.js
import { ref, computed } from 'vue'export function useWeather() {const temperature = ref(20)const conditions = ref('晴朗')const loading = ref(false)// 计算温度描述,带有幽默感const temperatureDescription = computed(() => {if (temperature.value < 0) return '冻得连北极熊都想搬家了🥶'if (temperature.value < 10) return '冷得让人怀疑人生的选择🧣'if (temperature.value < 20) return '有点凉,但不至于让你后悔出门🧥'if (temperature.value < 30) return '完美的温度,除非你是个企鹅🌞'if (temperature.value < 35) return '热得让人想抱住冰箱不放手🥵'return '欢迎来到地狱的温度体验,鸡蛋可在地上煎熟🔥'})// 获取天气的有趣函数async function fetchWeather(city) {loading.value = true// 模拟API调用return new Promise((resolve) => {setTimeout(() => {// 随机生成有趣的天气数据const weathers = ['晴朗', '多云', '小雨', '大雨', '雷暴', '龙卷风', '暴风雪', '外星人入侵']const temp = Math.floor(Math.random() * 40) - 5temperature.value = tempconditions.value = weathers[Math.floor(Math.random() * weathers.length)]loading.value = falseresolve({temperature: temp,conditions: conditions.value,forecast: '明天可能会更糟,或者更好,谁知道呢?天气预报员也在猜!'})}, 800)})}return {temperature,conditions,loading,temperatureDescription,fetchWeather}
}

13.4 有趣的实战案例:《猫咪点餐系统》🐱

这是一个结合了Vue3核心概念的迷你应用,一个猫咪专属的餐厅点餐系统:

<template><div class="cat-restaurant"><h1>🐱 喵星餐厅 <span v-if="auth.isLoggedIn">- 欢迎回来, {{ auth.user?.name }}</span></h1><!-- 登录部分 --><div v-if="!auth.isLoggedIn" class="login-section"><h2>请先登录再点餐</h2><a-form><a-input v-model:value="loginForm.username" placeholder="用户名 (提示: admin)" /><a-input-password v-model:value="loginForm.password" placeholder="密码 (提示: password123)" /><a-button type="primary" @click="handleLogin" :loading="auth.isLoading">喵星人身份验证</a-button></a-form></div><!-- 点餐部分 --><div v-else><a-row :gutter="16"><a-col :span="16"><h2>今日菜单 <a-tag color="red">猫咪专属</a-tag></h2><a-list :data-source="menu" class="menu-list"><template #renderItem="{ item }"><a-list-item><a-card hoverable style="width: 100%"><template #title>{{ item.name }} <a-tag color="green" v-if="item.popular">爆款</a-tag></template><template #cover><div class="food-image">{{ item.emoji }}</div></template><template #extra><a-button type="primary" @click="addToCart(item)">添加 ({{ item.price }}猫币)</a-button></template><p>{{ item.description }}</p></a-card></a-list-item></template></a-list></a-col><a-col :span="8"><a-affix :offset-top="20"><a-card title="购物车🛒" class="cart"><template #extra><a-button danger @click="cart = []">清空</a-button></template><a-empty v-if="cart.length === 0" description="购物车空空如也,喵~" /><div v-else><div v-for="(item, index) in cart" :key="index" class="cart-item"><span>{{ item.emoji }} {{ item.name }}</span><span>{{ item.price }}猫币 <a-button type="text" danger @click="removeFromCart(index)">❌</a-button></span></div><a-divider /><div class="cart-total"><span>总计:</span><span>{{ totalPrice }}猫币</span></div><a-button type="primary" block @click="placeOrder">确认订单并支付</a-button></div></a-card></a-affix></a-col></a-row></div></div>
</template><script setup>
import { reactive, ref, computed } from 'vue'
import { useAuth } from './composables/useAuth'
import { message } from 'ant-design-vue'// 使用我们的认证Hook
const auth = useAuth()// 登录表单
const loginForm = reactive({username: '',password: ''
})// 处理登录
const handleLogin = async () => {const result = await auth.login(loginForm.username, loginForm.password)if (result.success) {message.success('登录成功!猫粮即将送达~')}
}// 菜单数据
const menu = ref([{ id: 1,name: '豪华金枪鱼大餐',emoji: '🐟',description: '新鲜金枪鱼切片,撒上猫薄荷,保证让您家主子舔碗舔到见底',price: 25,popular: true},{ id: 2,name: '鸡肉慕斯',emoji: '🍗',description: '细嫩鸡胸肉打成泥状,质地如慕斯般丝滑,好吃到喵星人打滚',price: 18,popular: false},{ id: 3,name: '三文鱼骨头汤',emoji: '🥣',description: '熬制8小时的三文鱼骨头汤,富含胶原蛋白,让猫主子毛发亮丽',price: 15,popular: false},{ id: 4,name: '皇家猫草沙拉',emoji: '🥬',description: '有机猫草配上少量牛油果,是减肥猫咪的健康选择',price: 12,popular: false},{ id: 5,name: '老鼠形状小饼干',emoji: '🐭',description: '外形酷似老鼠的猫饼干,让您的猫在吃零食的同时体验捕猎快感',price: 8,popular: true}
])// 购物车
const cart = ref([])// 添加到购物车
const addToCart = (item) => {cart.value.push({ ...item })message.success(`${item.name}已添加到购物车`)
}// 从购物车移除
const removeFromCart = (index) => {cart.value.splice(index, 1)
}// 计算总价
const totalPrice = computed(() => {return cart.value.reduce((total, item) => total + item.price, 0)
})// 下单
const placeOrder = () => {message.loading('正在处理您的订单...')setTimeout(() => {message.success('下单成功!您的猫咪美食即将送达,约15分钟后到达猫窝')cart.value = []}, 2000)
}
</script><style scoped>
.cat-restaurant {max-width: 1200px;margin: 0 auto;padding: 20px;
}.login-section {max-width: 400px;margin: 40px auto;text-align: center;
}.menu-list {margin-top: 20px;
}.food-image {height: 120px;display: flex;justify-content: center;align-items: center;background: #fafafa;font-size: 60px;
}.cart {margin-top: 20px;
}.cart-item {display: flex;justify-content: space-between;margin-bottom: 10px;
}.cart-total {display: flex;justify-content: space-between;font-weight: bold;margin-bottom: 15px;
}
</style>

13.5 你与Vue开发者的日常对话

初级开发者: "为什么我的数据不更新啊?"
高级开发者: "你是不是忘了用.value?"
初级开发者: "哦对..."初级开发者: "为什么按钮点击没反应?"
高级开发者: "你用的是@click还是v-on:click?"
初级开发者: "我用的onClick..."
高级开发者: "这是React...不是Vue..."产品经理: "这个功能不难吧,下午能上线吗?"
Vue开发者: "嗯,简单,借我一台量子计算机,下午给你。"面试官: "说说你对Vue生命周期的理解?"
聪明的应聘者: "就像人的一生:出生(setup),长牙(onBeforeMount),上学(onMounted),工作(onBeforeUpdate, onUpdated),退休(onBeforeUnmount),去世(onUnmounted),唯一不同的是Vue组件可以投胎转世(keep-alive激活)。"

第十四章:Vue3 纯语法讲解

14.1 模板语法

Vue3 的模板语法是基于 HTML 的扩展,支持动态绑定数据和事件。

文本插值
<template><p>{{ message }}</p>
</template><script setup>
import { ref } from 'vue'
const message = ref('Hello, Vue3!')
</script>
属性绑定
<template><img :src="imageUrl" :alt="altText" />
</template><script setup>
const imageUrl = 'https://example.com/image.png'
const altText = '示例图片'
</script>
条件渲染
<template><p v-if="isLoggedIn">欢迎回来!</p><p v-else>请登录。</p>
</template><script setup>
const isLoggedIn = false
</script>
列表渲染
<template><ul><li v-for="item in items" :key="item.id">{{ item.name }}</li></ul>
</template><script setup>
const items = [{ id: 1, name: '苹果' },{ id: 2, name: '香蕉' },{ id: 3, name: '橙子' }
]
</script>

14.2 指令

Vue 提供了一组内置指令,用于操作 DOM。

v-bind
<template><a :href="url">点击这里</a>
</template><script setup>
const url = 'https://vuejs.org'
</script>
v-on
<template><button @click="handleClick">点击我</button>
</template><script setup>
function handleClick() {alert('按钮被点击了!')
}
</script>
v-model
<template><input v-model="username" placeholder="请输入用户名" /><p>你好,{{ username }}</p>
</template><script setup>
import { ref } from 'vue'
const username = ref('')
</script>

14.3 响应式系统

Vue3 的响应式系统基于 Proxy,提供了更强大的功能。

ref
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {count.value++
}
</script>
reactive
<script setup>
import { reactive } from 'vue'
const state = reactive({name: '张三',age: 25
})
</script>
computed
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
</script>
watch
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})
</script>

14.4 生命周期

Vue3 提供了更灵活的生命周期钩子。

常用生命周期
<script setup>
import { onMounted, onUnmounted } from 'vue'onMounted(() => {console.log('组件已挂载')
})onUnmounted(() => {console.log('组件已卸载')
})
</script>

14.5 Composition API

Composition API 是 Vue3 的核心特性,提供了更灵活的逻辑复用方式。

setup
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {count.value++
}
</script>
自定义组合式函数
// useCounter.js
import { ref } from 'vue'
export function useCounter() {const count = ref(0)function increment() {count.value++}return { count, increment }
}
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter()
</script>

14.6 模板引用

<template><div ref="box">我是一个盒子</div>
</template><script setup>
import { ref, onMounted } from 'vue'
const box = ref(null)
onMounted(() => {console.log(box.value) // 访问 DOM 元素
})
</script>

14.7 动态组件

<template><component :is="currentComponent"></component>
</template><script setup>
import { ref } from 'vue'
const currentComponent = ref('MyComponent')
</script>

14.8 插槽

<template><BaseLayout><template #header>这是头部</template><template #default>这是内容</template><template #footer>这是底部</template></BaseLayout>
</template><script>
export default {components: {BaseLayout: {template: `<div><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer></div>`}}
}
</script>

视图组件最佳实践

1. 语义化视图组件

Vue3 推荐使用语义化的视图组件来构建界面:

<template><view class="container"><header class="header"><text class="title">页面标题</text></header><main class="content"><text class="description">主要内容区域</text><button class="action-button">操作按钮</button></main><footer class="footer"><text class="copyright">© 2024 版权所有</text></footer></view>
</template>
2. 视图组件性能优化
  1. 避免过度嵌套
<!-- 不推荐 -->
<view><view><view><view>内容</view></view></view>
</view><!-- 推荐 -->
<view class="container"><text>内容</text>
</view>
  1. 使用 v-show 代替 v-if
<!-- 频繁切换时使用 v-show -->
<view v-show="isVisible">频繁切换的内容</view><!-- 条件渲染使用 v-if -->
<view v-if="shouldRender">一次性渲染的内容</view>
  1. 合理使用视图组件缓存
<template><keep-alive><view v-if="isActive"><!-- 需要缓存的内容 --></view></keep-alive>
</template>
3. 视图组件样式最佳实践
  1. 使用 BEM 命名规范
<template><view class="card"><view class="card__header"><text class="card__title">标题</text></view><view class="card__content"><text class="card__text">内容</text></view></view>
</template><style scoped>
.card {/* 卡片基础样式 */
}.card__header {/* 卡片头部样式 */
}.card__title {/* 卡片标题样式 */
}.card__content {/* 卡片内容样式 */
}.card__text {/* 卡片文本样式 */
}
</style>
  1. 使用 CSS 变量实现主题定制
<template><view class="theme-container"><text class="theme-text">主题文本</text></view>
</template><style>
:root {--primary-color: #3498db;--text-color: #333;--spacing-unit: 8px;
}.theme-container {padding: var(--spacing-unit);color: var(--text-color);
}.theme-text {color: var(--primary-color);
}
</style>
http://www.xdnf.cn/news/4019.html

相关文章:

  • GJOI 4.29 题解
  • 利用 Python pyttsx3实现文字转语音(TTS)
  • 9.进程控制(上)
  • linux 历史记录命令
  • Python爬虫(18)反爬攻防战:动态IP池构建与代理IP实战指南(突破95%反爬封禁率)
  • 全局过滤器与局部过滤器: Vue中的文本格式化工具
  • Python中的JSON库,详细介绍与代码示例
  • STC单片机与淘晶驰串口屏通讯例程之01【新建HDMI工程】
  • 计算机视觉与深度学习 | 图像匹配算法综述
  • Spring Boot 加载application.properties或application.yml配置文件的位置顺序。
  • Qwen3 性价比新王 Qwen3-30B-A3B 本地私有化部署,可灵活切换思考模式
  • 信息系统项目管理师-软考高级(软考高项)​​​​​​​​​​​2025最新(九)
  • Qml组件之AnimatedImage
  • 牛客1018逆序数-归并排序
  • 从入门到登峰-嵌入式Tracker定位算法全景之旅 Part 5 |地图匹配与轻量 SLAM:HMM/Viterbi 与简化图优化
  • 【PaaS与AI融合】MLOps平台的架构设计
  • DHCP服务器配置
  • PHP的现代复兴:从脚本语言到企业级服务端引擎的演进之路-优雅草卓伊凡
  • HTTP协议
  • 如何判断node节点是否启用cgroup?
  • 深入浅出数据库规范化的三大范式
  • 网络传输中字节序
  • 线程局部存储----TLS
  • seaborn
  • suna工具调用可视化界面实现原理分析(二)
  • 黑马点评day02(缓存)
  • 五一の自言自语 2025/5/5
  • 基于python的哈希查表搜索特定文件
  • 【C/C++】各种概念联系及辨析
  • Cadence高速系统设计流程及工具使用