Vue.js 从入门到企业级实战:核心原理、工程化与性能优化全解析
前言
Vue.js 作为前端三大主流框架之一,以其「渐进式框架」的特性、简洁的 API 和优秀的易用性,成为前端开发者的首选技术栈。本学期围绕 Vue.js 展开了系统性学习,从基础语法、组件化开发,到 Vue 3 的 Composition API、Pinia 状态管理、工程化构建,再到企业级项目中的性能优化、跨端适配,形成了完整的技术闭环。本文将以「原理 + 实战 + 工程化」为核心,结合 10 + 实战场景、百余个可落地代码片段,深度解析 Vue.js 的核心知识点与企业级应用技巧,全文超 10000 字,力求覆盖从入门到进阶的全维度内容。
一、Vue.js 核心认知:从设计理念到生态体系
1.1 Vue.js 的核心特性与设计理念
Vue.js 的核心设计理念是「渐进式框架」(Progressive Framework),即开发者可以根据需求逐步引入 Vue 的功能模块,而非一次性引入全部特性。核心特性包括:
- 数据驱动视图:基于 MVVM(Model-View-ViewModel)架构,通过数据绑定实现视图与数据的双向同步,无需手动操作 DOM;
- 组件化开发:将页面拆分为独立、可复用的组件,降低代码耦合度,提升维护性;
- 轻量级:核心库仅关注视图层,体积小(Vue 3 生产环境包约 10KB),且可与其他库 / 现有项目无缝集成;
- 响应式系统:Vue 3 基于 Proxy 实现响应式,相比 Vue 2 的 Object.defineProperty,支持数组、新增属性的监听,且性能更优;
- 指令系统:提供 v-if、v-for、v-bind、v-on 等指令,简化 DOM 操作;
- 生态完善:配套 Vue Router(路由)、Pinia/Vuex(状态管理)、Vue Test Utils(测试)、Vite(构建工具)等生态工具。
1.2 Vue 生态体系全景
| 核心模块 | 功能定位 | 版本适配 |
|---|---|---|
| Vue Core | 核心库(响应式、组件、指令等) | Vue 2 / Vue 3 |
| Vue Router | 路由管理,实现单页应用(SPA) | Vue Router 3(Vue 2)/ Vue Router 4(Vue 3) |
| Pinia | 新一代状态管理库,替代 Vuex | Vue 3(推荐) |
| Vuex | 传统状态管理库 | Vue 2 / Vue 3(兼容) |
| Vite | 极速构建工具,替代 Webpack | Vue 3(推荐) |
| Vue CLI | 脚手架工具 | Vue 2 / Vue 3 |
| Element Plus | 企业级 UI 组件库 | Vue 3 |
| Element UI | 企业级 UI 组件库 | Vue 2 |
| Vue Test Utils | 单元测试工具 | Vue 2 / Vue 3 |
| Vue Devtools | 调试工具 | Vue 2 / Vue 3 |
1.3 Vue 2 vs Vue 3 核心差异
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| 核心架构 | Options API(选项式 API) | Composition API(组合式 API)+ Options API(兼容) |
| 响应式原理 | Object.defineProperty(仅支持对象属性监听) | Proxy(支持对象、数组、新增属性监听) |
| 性能 | 虚拟 DOM 重写前,性能一般 | 虚拟 DOM 重写,编译优化,性能提升 50%+ |
| 体积 | 约 20KB(生产版) | 约 10KB(生产版,支持 Tree-Shaking) |
| 类型支持 | 弱 TypeScript 支持 | 原生 TypeScript 支持,类型推断更完善 |
| 生命周期 | beforeCreate/created 等 | setup 替代 beforeCreate/created,其余生命周期前缀加 on(如 onMounted) |
| 多根节点 | 不支持(必须有唯一根节点) | 支持多根节点(Fragment) |
二、Vue 3 基础核心:从语法到响应式原理
2.1 环境搭建:Vite+Vue 3 工程初始化
2.1.1 快速创建项目
bash
运行
# 初始化Vite+Vue 3项目(npm)
npm create vite@latest vue3-demo -- --template vue-ts
# 进入项目目录
cd vue3-demo
# 安装依赖
npm install
# 启动开发服务
npm run dev
2.1.2 工程结构解析(Vite+Vue 3+TS)
plaintext
vue3-demo/
├── node_modules/ // 依赖包
├── public/ // 静态资源(不参与打包,如favicon.ico)
├── src/
│ ├── api/ // 接口请求封装
│ ├── assets/ // 静态资源(图片、样式,参与打包)
│ ├── components/ // 通用组件(全局/业务组件)
│ ├── hooks/ // 自定义Hook(复用Composition API逻辑)
│ ├── router/ // 路由配置
│ ├── store/ // Pinia状态管理
│ ├── types/ // TypeScript类型定义
│ ├── utils/ // 工具类(axios、时间、加密等)
│ ├── views/ // 页面视图组件
│ ├── App.vue // 根组件
│ ├── main.ts // 入口文件
│ └── env.d.ts // 环境类型声明
├── .eslintrc.cjs // ESLint配置
├── .prettierrc // Prettier配置
├── index.html // 入口HTML(Vite特有)
├── package.json // 依赖与脚本配置
├── tsconfig.json // TypeScript配置
└── vite.config.ts // Vite配置
2.1.3 核心配置文件详解
vite.config.ts(Vite 核心配置)
typescript
运行
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
// 插件配置
plugins: [vue()],
// 路径别名(解决@路径引用)
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
// 开发服务器配置
server: {
port: 8080, // 端口
host: '0.0.0.0', // 允许外部访问
open: true, // 启动后自动打开浏览器
proxy: {
// 接口代理(解决跨域)
'/api': {
target: 'http://localhost:8081', // 后端接口地址
changeOrigin: true, // 开启跨域
rewrite: (path) => path.replace(/^\/api/, '') // 重写路径
}
}
},
// 构建配置
build: {
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
minify: 'terser', // 压缩方式
terserOptions: {
// 移除console和debugger
compress: {
drop_console: true,
drop_debugger: true
}
}
}
})
tsconfig.json(TypeScript 配置)
json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true, // 开启严格类型检查
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
// 路径别名映射
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
2.2 Vue 3 核心语法:Composition API 全解析
Vue 3 的核心升级是引入 Composition API,相比 Vue 2 的 Options API,它解决了「逻辑复用难」「大型组件代码分散」的问题,核心优势是逻辑聚合和类型友好。
2.2.1 入口:setup 函数
setup 是 Composition API 的入口函数,执行时机在 beforeCreate 之前,此时 this 指向 undefined,不能访问 this。
vue
<template>
<div>{{ count }} - {{ doubleCount }}</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// 1. 响应式变量(基础类型)
const count = ref(0)
// 2. 计算属性
const doubleCount = computed(() => count.value * 2)
// 3. 方法
const increment = () => {
count.value++
}
// 4. 生命周期钩子
onMounted(() => {
console.log('组件挂载完成')
increment()
})
</script>
2.2.2 响应式核心:ref 与 reactive
| 类型 | 适用场景 | 语法特点 |
|---|---|---|
| ref | 基础类型(Number/String/Boolean)、单值对象 | 通过.value 访问 / 修改值,模板中自动解包 |
| reactive | 复杂对象 / 数组 | 直接访问属性,无需.value,不支持基础类型 |
实战代码:ref 与 reactive 的使用与区别
vue
<template>
<div>
<!-- ref模板自动解包,无需.value -->
<p>基础类型ref:{{ num }}</p>
<!-- reactive直接访问属性 -->
<p>复杂对象reactive:{{ user.name }} - {{ user.age }}</p>
<!-- ref包裹对象,模板需.value(或用reactive) -->
<p>ref包裹对象:{{ obj.value.msg }}</p>
<button @click="updateData">更新数据</button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
// 1. ref:基础类型
const num = ref(10)
// 2. reactive:复杂对象
const user = reactive({
name: 'Vue开发者',
age: 25
})
// 3. ref:包裹对象(不推荐,优先用reactive)
const obj = ref({
msg: 'Hello Vue 3'
})
// 4. 更新数据
const updateData = () => {
num.value += 1 // ref需.value
user.age += 1 // reactive直接修改
obj.value.msg = 'Updated' // ref对象需.value
}
</script>
2.2.3 计算属性:computed
computed 用于定义「基于其他响应式数据派生的属性」,具备缓存特性(仅依赖数据变化时重新计算)。
vue
<template>
<div>
<p>原始价格:{{ price }}</p>
<p>折扣价格(9折):{{ discountPrice }}</p>
<p>税后价格(13%):{{ taxPrice }}</p>
<button @click="price += 10">提价10元</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const price = ref(100)
// 只读计算属性(最常用)
const discountPrice = computed(() => {
return (price.value * 0.9).toFixed(2)
})
// 可写计算属性(少用)
const taxPrice = computed({
get: () => (price.value * 1.13).toFixed(2),
set: (newValue) => {
price.value = Number((Number(newValue) / 1.13).toFixed(2))
}
})
// 触发可写计算属性的setter
const setTaxPrice = () => {
taxPrice.value = '124.3'
}
</script>
2.2.4 侦听器:watch 与 watchEffect
watch 用于「显式监听指定响应式数据」,watchEffect 用于「隐式监听所有用到的响应式数据」,核心区别:
- watch:懒执行(首次不执行),可获取新旧值,需指定监听源;
- watchEffect:立即执行,自动收集依赖,无法获取旧值。
实战代码:watch 与 watchEffect
vue
<template>
<div>
<p>用户名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<button @click="user.name = '新用户名'">修改用户名</button>
<button @click="user.age += 1">增加年龄</button>
</div>
</template>
<script setup lang="ts">
import { reactive, watch, watchEffect } from 'vue'
const user = reactive({
name: '初始用户名',
age: 20
})
// 1. watch:监听单个属性
watch(() => user.name, (newVal, oldVal) => {
console.log('用户名变化:', oldVal, '→', newVal)
}, {
immediate: true, // 首次执行
deep: false // 基础类型无需深度监听
})
// 2. watch:监听多个属性
watch([() => user.name, () => user.age], ([newName, newAge], [oldName, oldAge]) => {
console.log('用户名/年龄变化:', oldName, oldAge, '→', newName, newAge)
})
// 3. watch:深度监听对象(不推荐,优先监听具体属性)
watch(user, (newVal, oldVal) => {
console.log('user对象变化', newVal)
}, { deep: true })
// 4. watchEffect:自动收集依赖
const stopWatch = watchEffect(() => {
console.log('watchEffect:', user.name, user.age)
// 当user.name或user.age变化时触发
})
// 停止监听(如组件卸载时)
// stopWatch()
</script>
2.2.5 生命周期钩子
Vue 3 的生命周期钩子通过「on + 钩子名」的方式调用,且 setup 中无需区分 created/beforeCreate(setup 本身替代这两个钩子)。
| Vue 2 钩子 | Vue 3 钩子(Composition API) | 执行时机 |
|---|---|---|
| beforeCreate | setup(替代) | 组件实例创建前 |
| created | setup(替代) | 组件实例创建后 |
| beforeMount | onBeforeMount | 组件挂载前 |
| mounted | onMounted | 组件挂载后 |
| beforeUpdate | onBeforeUpdate | 组件更新前 |
| updated | onUpdated | 组件更新后 |
| beforeUnmount | onBeforeUnmount | 组件卸载前 |
| unmounted | onUnmounted | 组件卸载后 |
| errorCaptured | onErrorCaptured | 捕获子组件错误时 |
实战代码:生命周期钩子使用
vue
<template>
<div>生命周期演示</div>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
// 挂载前
onBeforeMount(() => {
console.log('组件即将挂载')
})
// 挂载后(常用:请求数据、初始化第三方库)
onMounted(() => {
console.log('组件挂载完成')
// 示例:请求列表数据
// fetchData()
})
// 更新前
onBeforeUpdate(() => {
console.log('组件即将更新')
})
// 更新后
onUpdated(() => {
console.log('组件更新完成')
})
// 卸载前(常用:清除定时器、取消请求、关闭WebSocket)
onBeforeUnmount(() => {
console.log('组件即将卸载')
// 示例:清除定时器
// clearInterval(timer)
})
// 卸载后
onUnmounted(() => {
console.log('组件卸载完成')
})
</script>
2.2.6 组件通信:Vue 3 全方案
组件通信是 Vue 开发的核心场景,Vue 3 支持多种通信方式,适配不同场景:
| 通信方式 | 适用场景 | 核心语法 |
|---|---|---|
| Props | 父传子 | 子组件 defineProps,父组件 v-bind |
| Emits | 子传父 | 子组件 defineEmits,父组件 v-on |
| v-model | 父子双向绑定 | 子组件 defineProps+defineEmits,父组件 v-model |
| provide/inject | 跨层级通信(爷孙 / 深层) | 父组件 provide,子组件 inject |
| Pinia | 全局状态共享 | 全局 Store,任意组件访问 / 修改 |
| 事件总线 | 非父子通信(Vue 3 需手动实现) | mitt 库 |
实战 1:Props+Emits(基础父子通信)
vue
<!-- 父组件 Parent.vue -->
<template>
<div>
<h3>父组件:{{ parentMsg }}</h3>
<Child
:msg="parentMsg"
@change-msg="handleChangeMsg"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const parentMsg = ref('父组件传递的消息')
const handleChangeMsg = (newMsg: string) => {
parentMsg.value = newMsg
}
</script>
<!-- 子组件 Child.vue -->
<template>
<div>
<p>子组件接收:{{ msg }}</p>
<button @click="sendToParent">向父组件发送消息</button>
</div>
</template>
<script setup lang="ts">
// 定义接收的Props
const props = defineProps<{
msg: string
}>()
// 定义触发的事件
const emit = defineEmits<{
(e: 'change-msg', newMsg: string): void
}>()
const sendToParent = () => {
emit('change-msg', '子组件修改后的消息')
}
</script>
实战 2:v-model(父子双向绑定)
vue
<!-- 父组件 Parent.vue -->
<template>
<div>
<h3>父组件v-model:{{ inputValue }}</h3>
<Child v-model="inputValue" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const inputValue = ref('初始值')
</script>
<!-- 子组件 Child.vue -->
<template>
<input
type="text"
:value="modelValue"
@input="handleInput"
/>
</template>
<script setup lang="ts">
// 定义v-model的Props(默认modelValue)
const props = defineProps<{
modelValue: string
}>()
// 定义v-model的事件(默认update:modelValue)
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
const handleInput = (e: Event) => {
const target = e.target as HTMLInputElement
emit('update:modelValue', target.value)
}
</script>
实战 3:provide/inject(跨层级通信)
vue
<!-- 根组件 App.vue -->
<template>
<Parent />
</template>
<script setup lang="ts">
import { provide, ref } from 'vue'
import Parent from './Parent.vue'
// 提供数据(可提供响应式数据)
const theme = ref('dark')
provide('theme', theme)
// 提供方法
provide('changeTheme', (newTheme: string) => {
theme.value = newTheme
})
</script>
<!-- 子组件 Parent.vue -->
<template>
<Child />
</template>
<script setup lang="ts">
import Child from './Child.vue'
// 无需处理,直接透传
</script>
<!-- 孙组件 Child.vue -->
<template>
<div :class="`theme-${theme}`">
<p>当前主题:{{ theme }}</p>
<button @click="changeTheme('light')">切换浅色主题</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
// 注入数据
const theme = inject<string>('theme', 'light') // 第二个参数是默认值
// 注入方法
const changeTheme = inject<(newTheme: string) => void>('changeTheme', () => {})
</script>
<style>
.theme-dark {
background: #333;
color: #fff;
}
.theme-light {
background: #fff;
color: #333;
}
</style>
实战 4:事件总线(mitt 库)
bash
运行
# 安装mitt
npm install mitt
typescript
运行
// src/utils/bus.ts:封装事件总线
import mitt from 'mitt'
type Events = {
'msg-change': string
'user-login': { id: number; name: string }
}
const bus = mitt<Events>() // 类型化事件总线
export default bus
vue
<!-- 发送方组件 Sender.vue -->
<template>
<button @click="sendMsg">发送消息</button>
</template>
<script setup lang="ts">
import bus from '@/utils/bus'
const sendMsg = () => {
// 发送事件
bus.emit('msg-change', '来自Sender的消息')
bus.emit('user-login', { id: 1, name: 'Vue用户' })
}
</script>
<!-- 接收方组件 Receiver.vue -->
<template>
<div>
<p>接收的消息:{{ msg }}</p>
<p>登录用户:{{ user?.name }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import bus from '@/utils/bus'
const msg = ref('')
const user = ref<{ id: number; name: string } | null>(null)
onMounted(() => {
// 监听事件
const msgHandler = (content: string) => {
msg.value = content
}
const userHandler = (userInfo) => {
user.value = userInfo
}
bus.on('msg-change', msgHandler)
bus.on('user-login', userHandler)
// 组件卸载时移除监听(避免内存泄漏)
onUnmounted(() => {
bus.off('msg-change', msgHandler)
bus.off('user-login', userHandler)
})
})
</script>
2.3 Vue 3 响应式原理深度解析
Vue 3 的响应式系统基于 ES6 的 Proxy 实现,核心分为「依赖收集」和「触发更新」两个阶段。
2.3.1 核心原理简化实现
typescript
运行
// 简化版响应式原理(理解用)
// 1. 存储依赖的映射:target -> key -> effect函数
const targetMap = new WeakMap<object, Map<string | symbol, Set<Function>>>()
// 2. 当前活跃的effect函数
let activeEffect: Function | null = null
// 3. effect函数:执行并收集依赖
function effect(fn: Function) {
activeEffect = fn
// 执行fn,触发Proxy的getter,收集依赖
fn()
activeEffect = null
}
// 4. 收集依赖
function track(target: object, key: string | symbol) {
if (!activeEffect) return
// 获取target的依赖映射
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取key的effect集合
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
// 添加当前effect到集合
deps.add(activeEffect)
}
// 5. 触发更新
function trigger(target: object, key: string | symbol) {
// 获取target的依赖映射
const depsMap = targetMap.get(target)
if (!depsMap) return
// 获取key的effect集合
const deps = depsMap.get(key)
if (!deps) return
// 执行所有effect
deps.forEach(fn => fn())
}
// 6. 响应式核心:Proxy
function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 收集依赖
track(target, key)
return res
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
const res = Reflect.set(target, key, value, receiver)
// 只有值变化时触发更新
if (oldValue !== value) {
trigger(target, key)
}
return res
}
})
}
// 测试
const obj = reactive({ count: 0 })
// 定义effect,当count变化时执行
effect(() => {
console.log('count变化:', obj.count)
})
obj.count++ // 输出:count变化:1
obj.count++ // 输出:count变化:2
2.3.2 ref 的底层实现
ref 的底层是对基础类型的「包裹对象」,通过 Proxy 监听.value 属性:
typescript
运行
function ref<T>(value: T) {
// 包裹对象
const wrapper = {
_value: value
}
// 转为响应式
return new Proxy(wrapper, {
get(target, key) {
if (key === 'value') {
track(target, '_value')
return target._value
}
return Reflect.get(target, key)
},
set(target, key, value) {
if (key === 'value') {
target._value = value
trigger(target, '_value')
return true
}
return Reflect.set(target, key, value)
}
})
}
三、Vue 工程化开发:从路由到状态管理
3.1 Vue Router 4:单页应用核心
Vue Router 是 Vue 官方的路由管理器,实现单页应用(SPA)的页面跳转,Vue 3 适配 Vue Router 4。
3.1.1 路由基础配置
bash
运行
# 安装Vue Router 4
npm install vue-router@4
typescript
运行
// src/router/index.ts:路由核心配置
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '@/views/Home.vue'
import Login from '@/views/Login.vue'
// 静态路由(无需权限)
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home' // 重定向
},
{
path: '/home',
name: 'Home',
component: Home,
meta: {
title: '首页',
keepAlive: true // 开启缓存
}
},
{
path: '/login',
name: 'Login',
component: Login,
hidden: true // 侧边栏隐藏
},
{
path: '/detail/:id', // 动态路由参数
name: 'Detail',
component: () => import('@/views/Detail.vue'), // 懒加载
props: true // 开启props传参
},
{
path: '/:pathMatch(.*)*', // 404路由
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), // HTML5 History模式
routes: constantRoutes,
scrollBehavior(to, from, savedPosition) {
// 滚动行为:返回顶部
return savedPosition || { top: 0 }
}
})
export default router
typescript
运行
// src/main.ts:注册路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
vue
<!-- src/App.vue:路由出口 -->
<template>
<router-view />
</template>
3.1.2 路由核心功能实战
实战 1:路由跳转与传参
vue
<!-- 声明式跳转 -->
<template>
<div>
<!-- 基础跳转 -->
<router-link to="/home">首页</router-link>
<!-- 动态参数跳转 -->
<router-link :to="{ name: 'Detail', params: { id: 1 } }">详情页</router-link>
<!-- 查询参数跳转 -->
<router-link :to="{ path: '/detail/1', query: { type: 'vue' } }">带查询参数的详情页</router-link>
</div>
</template>
<!-- 编程式跳转 -->
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
// 获取路由实例
const router = useRouter()
// 获取当前路由信息
const route = useRoute()
// 基础跳转
const toHome = () => {
router.push('/home')
}
// 命名路由跳转(推荐)
const toDetail = () => {
router.push({
name: 'Detail',
params: { id: 1 },
query: { type: 'vue' }
})
}
// 替换历史记录(不新增历史条目)
const replaceToHome = () => {
router.replace('/home')
}
// 前进/后退
const goBack = () => {
router.go(-1) // 后退一步
}
// 获取参数
console.log(route.params.id) // 动态参数
console.log(route.query.type) // 查询参数
</script>
实战 2:路由守卫(权限控制)路由守卫分为「全局守卫」「路由独享守卫」「组件内守卫」,核心用于权限控制、登录验证、页面标题设置等。
typescript
运行
// src/router/index.ts:全局守卫
// 1. 全局前置守卫(常用:登录验证)
router.beforeEach((to, from, next) => {
// 设置页面标题
if (to.meta.title) {
document.title = to.meta.title as string
}
// 登录验证:访问需要登录的路由,未登录则跳转登录页
const isLogin = localStorage.getItem('token')
const needLogin = !to.path.includes('/login') // 除了登录页都需要登录
if (needLogin && !isLogin) {
next('/login')
} else {
next() // 放行
}
})
// 2. 全局解析守卫
router.beforeResolve((to, from, next) => {
// 在所有组件内守卫和异步路由组件解析之后执行
next()
})
// 3. 全局后置守卫(无next,用于统计、埋点)
router.afterEach((to, from) => {
console.log('页面跳转完成:', from.path, '→', to.path)
})
typescript
运行
// 路由独享守卫
const routes = [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => {
// 仅对当前路由生效
console.log('进入首页前执行')
next()
}
}
]
vue
<!-- 组件内守卫 -->
<template>
<div>详情页</div>
</template>
<script setup lang="ts">
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
// 进入组件前(此时组件实例未创建,无法访问this/setup变量)
onBeforeRouteEnter((to, from, next) => {
console.log('进入详情页前')
// 通过next回调访问组件实例
next(vm => {
console.log('组件实例:', vm)
})
})
// 路由参数更新时(如从/detail/1到/detail/2)
onBeforeRouteUpdate((to, from, next) => {
console.log('详情页参数更新:', from.params.id, '→', to.params.id)
next()
})
// 离开组件前(常用:确认是否离开)
onBeforeRouteLeave((to, from, next) => {
const confirm = window.confirm('是否确认离开?')
if (confirm) {
next()
} else {
next(false) // 取消离开
}
})
</script>
3.1.3 路由懒加载与代码分割
路由懒加载通过「动态 import」实现,减少首屏加载体积,提升加载速度:
typescript
运行
// 基础懒加载
const Home = () => import('@/views/Home.vue')
// 带分组的懒加载(打包时合并到同一chunk)
const Home = () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
const About = () => import(/* webpackChunkName: "home" */ '@/views/About.vue')
3.2 Pinia:新一代状态管理
Pinia 是 Vue 官方推荐的状态管理库,替代 Vuex,核心优势:
- 支持 Vue 2/Vue 3,无需嵌套模块,扁平化设计;
- 原生 TypeScript 支持,类型推断完善;
- 简化 API,无需 mutations,直接修改状态;
- 支持组合式 API,逻辑复用更灵活;
- 体积更小(约 1KB),性能更优。
3.2.1 Pinia 基础配置
bash
运行
# 安装Pinia
npm install pinia
typescript
运行
// src/store/index.ts:创建Pinia实例
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
typescript
运行
// src/main.ts:注册Pinia
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './store'
const app = createApp(App)
app.use(pinia)
app.mount('#app')
3.2.2 Pinia 核心用法:定义 Store
typescript
运行
// src/store/modules/user.ts:用户Store
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import request from '@/utils/request'
// 定义Store(第一个参数是唯一ID)
export const useUserStore = defineStore('user', () => {
// 1. 状态(替代Vuex的state)
const token = ref(localStorage.getItem('token') || '')
const userInfo = ref<{ id: number; name: string; avatar: string } | null>(null)
// 2. 计算属性(替代Vuex的getters)
const isLogin = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.name || '未登录')
// 3. 方法(替代Vuex的actions,支持同步/异步)
const login = async (username: string, password: string) => {
const res = await request.post('/api/auth/login', { username, password })
token.value = res.data.accessToken
// 持久化Token
localStorage.setItem('token', token.value)
// 获取用户信息
await getUserInfo()
}
const getUserInfo = async () => {
const res = await request.get('/api/user/info')
userInfo.value = res.data
}
const logout = () => {
token.value = ''
userInfo.value = null
localStorage.removeItem('token')
}
// 暴露状态、计算属性、方法
return {
token,
userInfo,
isLogin,
userName,
login,
getUserInfo,
logout
}
})
3.2.3 Pinia 组件使用
vue
<template>
<div>
<p>用户名:{{ userName }}</p>
<p v-if="isLogin">已登录</p>
<button v-if="!isLogin" @click="handleLogin">登录</button>
<button v-else @click="handleLogout">退出登录</button>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'
// 获取Store实例
const userStore = useUserStore()
// 访问状态(直接访问,无需.value)
const { userName, isLogin } = userStore
// 方法调用
const handleLogin = async () => {
await userStore.login('admin', '123456')
}
const handleLogout = () => {
userStore.logout()
}
</script>
3.2.4 Pinia 高级特性
实战 1:Store 持久化(pinia-plugin-persistedstate)
bash
运行
# 安装持久化插件
npm install pinia-plugin-persistedstate
typescript
运行
// src/store/index.ts:注册插件
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia
typescript
运行
// src/store/modules/user.ts:开启持久化
export const useUserStore = defineStore('user', () => {
// ... 状态定义
}, {
// 持久化配置
persist: {
key: 'user-store', // 存储的key
storage: localStorage, // 存储方式(localStorage/sessionStorage)
paths: ['token', 'userInfo.name'] // 只持久化token和userInfo.name
}
})
实战 2:Store 间通信
typescript
运行
// src/store/modules/cart.ts:购物车Store
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {
const cartList = ref<{ id: number; name: string; price: number }[]>([])
// 依赖其他Store
const userStore = useUserStore()
// 添加商品(仅登录用户可操作)
const addGoods = (goods: { id: number; name: string; price: number }) => {
if (!userStore.isLogin) {
alert('请先登录')
return
}
cartList.value.push(goods)
}
return {
cartList,
addGoods
}
})
实战 3:批量修改状态($patch)
vue
<script setup lang="ts">
import { useCartStore } from '@/store/modules/cart'
const cartStore = useCartStore()
// 批量修改状态(性能更优)
const batchUpdateCart = () => {
cartStore.$patch({
cartList: [
...cartStore.cartList,
{ id: 2, name: 'Vue实战教程', price: 99 }
]
})
// 或函数式修改(适合复杂逻辑)
cartStore.$patch((state) => {
state.cartList.push({ id: 3, name: 'Pinia教程', price: 88 })
})
}
</script>
四、Vue 企业级实战:组件封装、性能优化与跨端
4.1 通用组件封装:从基础到高阶
组件封装是 Vue 工程化的核心,优秀的组件需满足「高内聚、低耦合、可复用、可配置」的原则。
4.1.1 基础组件封装:通用按钮
vue
<!-- src/components/MyButton.vue -->
<template>
<button
class="my-button"
:class="[
`my-button--${type}`,
`my-button--${size}`,
{ 'my-button--disabled': disabled },
{ 'my-button--loading': loading }
]"
:disabled="disabled || loading"
@click="handleClick"
>
<span v-if="loading" class="my-button__loading"></span>
<slot></slot>
</button>
</template>
<script setup lang="ts">
// 定义Props
const props = defineProps<{
type: 'primary' | 'success' | 'warning' | 'danger' | 'default'
size: 'large' | 'medium' | 'small'
disabled?: boolean
loading?: boolean
}>()
// 默认值
withDefaults(props, {
type: 'default',
size: 'medium',
disabled: false,
loading: false
})
// 定义Emits
const emit = defineEmits<{
(e: 'click', evt: MouseEvent): void
}>()
// 点击事件
const handleClick = (evt: MouseEvent) => {
if (!props.disabled && !props.loading) {
emit('click', evt)
}
}
</script>
<style scoped>
.my-button {
border: none;
border-radius: 4px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 16px;
font-size: 14px;
transition: all 0.2s;
}
/* 类型样式 */
.my-button--default {
background: #fff;
color: #333;
border: 1px solid #ddd;
}
.my-button--primary {
background: #409eff;
color: #fff;
}
.my-button--success {
background: #67c23a;
color: #fff;
}
.my-button--warning {
background: #e6a23c;
color: #fff;
}
.my-button--danger {
background: #f56c6c;
color: #fff;
}
/* 大小样式 */
.my-button--large {
height: 40px;
font-size: 16px;
}
.my-button--medium {
height: 32px;
}
.my-button--small {
height: 24px;
font-size: 12px;
padding: 0 8px;
}
/* 禁用/加载样式 */
.my-button--disabled, .my-button--loading {
cursor: not-allowed;
opacity: 0.6;
}
.my-button__loading {
width: 14px;
height: 14px;
border: 2px solid #fff;
border-top: 2px solid transparent;
border-radius: 50%;
animation: loading 1s linear infinite;
margin-right: 8px;
}
@keyframes loading {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
4.1.2 高阶组件封装:表格组件
vue
<!-- src/components/MyTable.vue -->
<template>
<div class="my-table">
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.prop" :width="column.width">
{{ column.label }}
</th>
<th v-if="showAction" width="120">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in data" :key="row[props.rowKey] || index">
<td v-for="column in columns" :key="column.prop">
<!-- 自定义列内容 -->
<slot :name="`cell-${column.prop}`" :row="row" :index="index">
{{ row[column.prop] }}
</slot>
</td>
<td v-if="showAction">
<slot name="action" :row="row" :index="index"></slot>
</td>
</tr>
<tr v-if="data.length === 0">
<td :colspan="columns.length + (showAction ? 1 : 0)" class="empty">
{{ emptyText }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
// 定义列类型
interface TableColumn {
prop: string
label: string
width?: string | number
}
// 定义Props
const props = defineProps<{
columns: TableColumn[]
data: Record<string, any>[]
rowKey?: string
showAction?: boolean
emptyText?: string
}>()
// 默认值
withDefaults(props, {
rowKey: 'id',
showAction: false,
emptyText: '暂无数据'
})
</script>
<style scoped>
.my-table {
width: 100%;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
}
.my-table table {
width: 100%;
border-collapse: collapse;
}
.my-table th, .my-table td {
padding: 12px;
border-bottom: 1px solid #eee;
text-align: left;
}
.my-table th {
background: #f5f5f5;
font-weight: 600;
}
.my-table .empty {
text-align: center;
color: #999;
padding: 40px 0;
}
</style>
组件使用示例
vue
<template>
<MyTable
:columns="columns"
:data="tableData"
show-action
>
<!-- 自定义列:状态 -->
<template #cell-status="{ row }">
<span :class="`status-${row.status}`">
{{ row.status === 'active' ? '活跃' : '禁用' }}
</span>
</template>
<!-- 操作列 -->
<template #action="{ row }">
<MyButton type="primary" size="small" @click="editRow(row)">编辑</MyButton>
<MyButton type="danger" size="small" @click="deleteRow(row)">删除</MyButton>
</template>
</MyTable>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import MyTable from '@/components/MyTable.vue'
import MyButton from '@/components/MyButton.vue'
// 列配置
const columns = ref([
{ prop: 'name', label: '姓名', width: '100px' },
{ prop: 'age', label: '年龄', width: '80px' },
{ prop: 'status', label: '状态' }
])
// 表格数据
const tableData = ref([
{ id: 1, name: 'Vue开发者', age: 25, status: 'active' },
{ id: 2, name: 'Pinia爱好者', age: 30, status: 'disabled' }
])
// 编辑行
const editRow = (row) => {
console.log('编辑:', row)
}
// 删除行
const deleteRow = (row) => {
tableData.value = tableData.value.filter(item => item.id !== row.id)
}
</script>
<style scoped>
.status-active {
color: #67c23a;
}
.status-disabled {
color: #999;
}
</style>
4.2 Vue 性能优化:从代码到构建
Vue 项目的性能优化需覆盖「代码层面」「构建层面」「运行时层面」,核心目标是「减少加载时间」「提升渲染性能」「降低内存占用」。
4.2.1 代码层面优化
1. 响应式数据优化
- 避免响应式数据嵌套过深:reactive 嵌套层级建议不超过 3 层;
- 非响应式数据无需 ref/reactive:如常量、一次性数据;
- 使用 shallowRef/shallowReactive:仅监听浅层属性,适合大型对象。
vue
<script setup lang="ts">
import { shallowRef, shallowReactive } from 'vue'
// 浅层响应式:仅监听obj.value的引用变化,不监听内部属性
const obj = shallowRef({ a: 1, b: { c: 2 } })
obj.value.a = 2 // 不触发更新
obj.value = { a: 3 } // 触发更新
// 浅层响应式:仅监听对象第一层属性
const user = shallowReactive({
name: 'Vue',
info: { age: 3 }
})
user.name = 'Vue 3' // 触发更新
user.info.age = 4 // 不触发更新
</script>
2. 渲染优化
- v-for 优化:必须加 key,避免使用 index 作为 key;
- v-if 与 v-for 优先级:避免同时使用(v-for 优先级更高,可将 v-if 提至外层);
- 虚拟列表:长列表使用 vue-virtual-scroller,只渲染可视区域;
- 避免不必要的渲染:使用 v-once、computed 缓存、memo 组件。
vue
<!-- v-for优化 -->
<template>
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
<!-- v-if与v-for分离 -->
<template v-if="showList">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</template>
<!-- v-once:只渲染一次 -->
<div v-once>{{ staticData }}</div>
<!-- memo:仅依赖变化时重新渲染 -->
<MyComponent v-memo="[props.a, props.b]" />
</template>
3. 事件与监听优化
- 移除无用的事件监听:组件卸载时移除 window / 第三方库的监听;
- 防抖 / 节流:高频事件(resize、scroll、input)添加防抖节流;
vue
<template>
<input @input="handleInput" />
</template>
<script setup lang="ts">
import { onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
// 防抖处理
const handleInput = debounce((e) => {
console.log(e.target.value)
}, 300)
// 移除监听
onUnmounted(() => {
handleInput.cancel()
})
</script>
4.2.2 构建层面优化
1. 按需引入
- UI 组件库按需引入:如 Element Plus、Ant Design Vue;
- 第三方库按需引入:如 lodash、echarts。
typescript
运行
// src/main.ts:Element Plus按需引入
import { createApp } from 'vue'
import App from './App.vue'
import { ElButton, ElTable } from 'element-plus'
import 'element-plus/es/components/button/style/css'
import 'element-plus/es/components/table/style/css'
const app = createApp(App)
app.component('ElButton', ElButton)
app.component('ElTable', ElTable)
app.mount('#app')
2. 资源压缩
- 开启 gzip/brotli 压缩:Nginx 配置或 Vite 插件;
- 图片压缩:使用 vite-plugin-imagemin;
- 代码压缩:Vite 默认开启 terser 压缩,移除 console/debugger。
typescript
运行
// vite.config.ts:开启gzip压缩
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
// gzip压缩
viteCompression({
algorithm: 'gzip',
threshold: 10240, // 大于10KB的文件压缩
deleteOriginFile: false // 不删除原文件
}),
// brotli压缩(比gzip更优)
viteCompression({
algorithm: 'brotliCompress',
ext: '.br',
threshold: 10240
})
]
})
3. 分包策略
- 拆分第三方库:将 node_modules 拆分为单独的 chunk;
- 拆分页面:不同页面拆分为不同的 chunk;
typescript
运行
// vite.config.ts:分包策略
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
// 分包
manualChunks: {
// 第三方库拆分
vendor: ['vue', 'vue-router', 'pinia'],
utils: ['lodash-es', 'axios']
}
}
}
}
})
4.2.3 运行时优化
1. 路由懒加载(已在 3.1.3 实现)
2. 缓存优化
- 组件缓存:使用 keep-alive 缓存频繁切换的组件;
- 接口缓存:使用 axios 拦截器缓存 GET 请求结果;
vue
<!-- keep-alive缓存组件 -->
<template>
<keep-alive include="Home,Detail" exclude="Login">
<router-view />
</keep-alive>
</template>
typescript
运行
// src/utils/request.ts:接口缓存
import axios from 'axios'
// 缓存容器
const cache = new Map()
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL
})
// 请求拦截器:缓存GET请求
request.interceptors.request.use((config) => {
if (config.method === 'get') {
const key = JSON.stringify({
url: config.url,
params: config.params
})
// 缓存命中,直接返回缓存数据
if (cache.has(key)) {
return Promise.reject({
type: 'cache',
data: cache.get(key)
})
}
}
return config
})
// 响应拦截器:存储GET请求缓存
request.interceptors.response.use((response) => {
if (response.config.method === 'get') {
const key = JSON.stringify({
url: response.config.url,
params: response.config.params
})
cache.set(key, response.data)
// 设置缓存过期时间(5分钟)
setTimeout(() => {
cache.delete(key)
}, 300000)
}
return response
}, (error) => {
// 缓存命中的错误处理
if (error.type === 'cache') {
return Promise.resolve({ data: error.data })
}
return Promise.reject(error)
})
export default request
4.3 Vue 跨端开发:从 Web 到移动端
Vue 的跨端生态完善,可通过不同方案实现「一套代码多端运行」:
4.3.1 Vue + Vant:移动端 Web 开发
Vant 是有赞开源的移动端 UI 组件库,适配 Vue 2/Vue 3,核心优势:轻量、易用、适配性好。
bash
运行
# 安装Vant 4(适配Vue 3)
npm install vant
typescript
运行
// src/main.ts:按需引入Vant
import { createApp } from 'vue'
import App from './App.vue'
import { Button, Cell, List } from 'vant'
import 'vant/lib/index.css'
const app = createApp(App)
app.use(Button)
app.use(Cell)
app.use(List)
app.mount('#app')
4.3.2 UniApp:Vue 跨端开发(小程序 / APP/H5)
UniApp 是基于 Vue 的跨端框架,支持一套代码编译为微信小程序、支付宝小程序、APP、H5 等。
- 核心语法:Vue 2/Vue 3(推荐 Vue 3 + Vite);
- 组件:扩展 Vue 组件,新增小程序特有的组件(如 swiper、picker);
- API:封装多端统一的 API(如 uni.request、uni.navigateTo)。
4.3.3 Vue + Electron:桌面应用开发
Electron 是基于 Chromium 和 Node.js 的桌面应用框架,可将 Vue 项目打包为 Windows/Mac/Linux 应用。
bash
运行
# 安装Electron
npm install electron --save-dev
javascript
运行
// electron/main.js:主进程
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true
}
})
// 加载Vue项目的dist目录
win.loadFile('dist/index.html')
// 开启开发者工具
win.webContents.openDevTools()
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
五、Vue 测试与部署:企业级工程闭环
5.1 Vue 单元测试:Vue Test Utils
Vue Test Utils 是 Vue 官方的单元测试库,适配 Vue 3,支持 Jest/Vitest。
bash
运行
# 安装依赖
npm install @vue/test-utils vitest jsdom --save-dev
typescript
运行
// vitest.config.ts:配置
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true
}
})
typescript
运行
// src/components/MyButton.test.ts:按钮组件测试
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import MyButton from './MyButton.vue'
describe('MyButton.vue', () => {
// 测试默认渲染
it('renders default button', () => {
const wrapper = mount(MyButton, {
slots: {
default: '默认按钮'
}
})
expect(wrapper.text()).toBe('默认按钮')
expect(wrapper.classes()).toContain('my-button--default')
})
// 测试类型
it('renders primary button', () => {
const wrapper = mount(MyButton, {
props: {
type: 'primary'
}
})
expect(wrapper.classes()).toContain('my-button--primary')
})
// 测试点击事件
it('emits click event', async () => {
const wrapper = mount(MyButton)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
// 测试禁用状态
it('disabled button not emit click', async () => {
const wrapper = mount(MyButton, {
props: {
disabled: true
}
})
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeFalsy()
})
})
5.2 Vue 项目部署:从打包到上线
5.2.1 打包构建
bash
运行
# 生产环境打包
npm run build
打包完成后生成 dist 目录,包含静态 HTML/CSS/JS 文件。
5.2.2 Nginx 部署
nginx
# nginx.conf配置
server {
listen 80;
server_name vue-demo.com;
# 根目录指向dist
root /usr/share/nginx/html/vue-demo/dist;
index index.html;
# 解决SPA路由刷新404
location / {
try_files $uri $uri/ /index.html;
}
# 开启gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
}
5.2.3 容器化部署(Docker)
dockerfile
# Dockerfile
# 阶段1:构建
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 阶段2:部署
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
bash
运行
# 构建镜像
docker build -t vue-demo:v1 .
# 运行容器
docker run -d -p 80:80 --name vue-demo vue-demo:v1
六、学习总结与进阶方向
6.1 学习总结
本学期围绕 Vue.js 的学习,从基础语法到企业级实战,核心收获:
- 核心原理层面:理解了 Vue 3 的响应式原理(Proxy)、虚拟 DOM、编译优化,掌握了 Composition API 的设计思想;
- 工程化层面:掌握了 Vite 构建、路由管理、状态管理、组件封装的企业级规范,能够搭建完整的 Vue 工程体系;
- 性能优化层面:掌握了从代码、构建、运行时的全维度优化技巧,能够解决实际项目中的性能瓶颈;
- 实战能力层面:能够独立开发组件、处理复杂业务逻辑、实现跨端适配,具备了企业级 Vue 开发的核心能力。
6.2 进阶方向
- 源码深度解析:深入学习 Vue 3 源码,理解编译过程、虚拟 DOMdiff 算法、响应式系统的底层实现;
- 跨端框架深入:深入学习 UniApp、Taro 等跨端框架,掌握多端适配的核心技巧;
- 前端工程化进阶:学习 Monorepo、微前端、CI/CD 等高级工程化方案;
- 性能优化进阶:学习 Web Worker、SSR(服务端渲染)、SSG(静态站点生成)等高级优化手段;
- Vue 生态扩展:学习 VueUse(Vue 工具库)、Nuxt.js(Vue 服务端渲染框架)等生态工具。
更多推荐


所有评论(0)