Vue笔记
Vue笔记
01 Vue概述
Vue 是一个 渐进式 JavaScript 框架 ,用于构建用户界面(UI)。它以简单、灵活和高性能著称,尤其适合初学者和需要快速开发的项目。
一、Vue 是什么?
• 渐进式框架 :可以从简单的功能开始(如渲染静态页面),逐步添加复杂功能(如路由、状态管理),无需一开始就掌握全部知识。
• 专注于视图层 :Vue 的核心库专注于 UI 渲染,但通过官方工具和插件(如 Vue Router、Pinia)支持构建完整的前端应用。
• 兼容性强 :支持直接嵌入现有项目,或作为独立应用开发。
二、Vue 的核心特性
响应式数据绑定
数据变化时,视图自动更新。例如,修改
data
中的值,页面会实时反映变化。<div id="app">{{ message }}</div> <script> const app = Vue.createApp({ data() { return { message: "Hello Vue!" }; }, }).mount("#app"); </script>
组件化开发
将 UI 拆分为独立、可复用的组件,每个组件包含自己的 HTML、CSS 和 JavaScript。
<template> <button @click="count++">点击次数:{{ count }}</button> </template> <script> export default { data() { return { count: 0 }; }, }; </script>
指令系统
Vue 提供简洁的指令(Directives)来操作 DOM:
•
v-bind
:绑定属性(如v-bind:href="url"
)•
v-model
:表单输入双向绑定•
v-for
:循环渲染列表•
v-if
/v-show
:条件渲染虚拟 DOM
Vue 通过虚拟 DOM 优化性能,仅更新变化的 DOM 部分,减少直接操作真实 DOM 的开销。
三、为什么选择 Vue?
易学易用
语法简洁,中文文档完善,学习曲线平缓。
灵活性
支持传统 HTML 开发,也支持结合现代工具链(如 Vite、Webpack)。
强大生态
• 官方库:Vue Router(路由)、Pinia(状态管理)
• 工具链:Vite(快速构建)、Vue Devtools(调试工具)
• 社区活跃:丰富的第三方组件库(如 Element Plus、Vant)。
四、快速上手 Vue 3
通过 CDN 引入
<script src="https://unpkg.com/vue@3"></script>
使用脚手架创建项目 (推荐正式开发)
安装 Node.js 后,运行:
npm create vue@latest # 或 yarn create vue
按提示选择需要的功能(路由、状态管理等)。
单文件组件(.vue 文件)
一个组件包含
<template>
,<script>
,<style>
三部分:<template> <h1>{{ title }}</h1> </template> <script> export default { data() { return { title: "Vue 3 入门" }; }, }; </script> <style scoped> h1 { color: blue; } </style>
五、学习资源
官方文档
•
• 教程、示例、API 参考齐全,适合系统学习。
推荐工具和库
• Vite:极速构建工具
• Pinia:新一代状态管理
• VueUse:常用工具函数集合
02 基础语法
以下是 Vue 的基本语法总结,涵盖 模板语法 、 指令 、 组件 和 核心功能 :
一、模板语法
1. 插值
<!-- 文本插值 -->
<p>{{ message }}</p>
<!-- 原始 HTML(慎用,避免 XSS 攻击) -->
<p v-html="rawHtml"></p>
<!-- 动态属性 -->
<a :href="url">链接</a>
2. 指令 (Directives)
•
v-bind
:动态绑定属性(可简写为
:
)
<img :src="imageUrl" :alt="description">
• v-model :表单双向绑定
<input v-model="inputText" type="text">
<select v-model="selectedOption">
<option value="A">选项A</option>
</select>
• v-if / v-else-if / v-else :条件渲染
<div v-if="score >= 90">优秀</div>
<div v-else-if="score >= 60">及格</div>
<div v-else>不及格</div>
•
v-show
:切换显示(通过 CSS
display
控制)
<div v-show="isVisible">显示/隐藏内容</div>
•
v-for
:列表渲染(必须加
:key
)
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
</ul>
二、事件处理
<!-- 内联事件 -->
<button @click="counter += 1">点击+1</button>
<!-- 方法调用 -->
<button @click="handleClick">点击事件</button>
<!-- 事件修饰符 -->
<form @submit.prevent="onSubmit">阻止默认行为</form>
<a @click.stop="doSomething">阻止事件冒泡</a>
export default {
methods: {
handleClick(event) {
console.log('点击事件', event.target);
},
onSubmit() {
console.log('表单提交');
}
}
}
三、计算属性和侦听器
1. 计算属性 (computed)
export default {
data() {
return { firstName: '张', lastName: '三' }
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
}
2. 侦听器 (watch)
export default {
data() {
return { count: 0 }
},
watch: {
count(newVal, oldVal) {
console.log(`计数从 ${oldVal} 变为 ${newVal}`);
}
}
}
四、组件基础
1. 定义组件
<!-- ChildComponent.vue -->
<template>
<div>
<h3>{{ title }}</h3>
<button @click="sendMessage">传递消息</button>
</div>
</template>
<script>
export default {
props: ['title'],
emits: ['message'],
methods: {
sendMessage() {
this.$emit('message', '来自子组件的消息');
}
}
}
</script>
2. 使用组件
<template>
<div>
<ChildComponent
title="子组件标题"
@message="handleMessage"
/>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
methods: {
handleMessage(msg) {
console.log(msg); // 输出:来自子组件的消息
}
}
}
</script>
五、生命周期钩子
钩子函数 | 触发时机 |
---|---|
beforeCreate | 实例初始化前 |
created | 实例创建完成(可访问数据) |
beforeMount | 挂载到 DOM 前 |
mounted | 挂载完成(可操作 DOM) |
beforeUpdate | 数据更新前 |
updated | 数据更新后 |
beforeUnmount | 实例销毁前 |
unmounted | 实例销毁后 |
六、常见注意事项
data
必须为函数组件中的
data
必须返回一个对象,避免数据共享:data() { return { count: 0 } }
v-for
必须加key
:key
帮助 Vue 高效更新虚拟 DOM:<div v-for="item in list" :key="item.id">{{ item.text }}</div>
避免直接修改
props
子组件应通过事件 (
$emit
) 通知父组件修改数据。
03 事件绑定
在 Vue 中,事件绑定是前端交互的核心功能之一。以下是 事件绑定 的完整指南,涵盖基础语法、修饰符、参数传递等核心内容:
一、基础事件绑定
1. 使用 v-on
或 @
简写
<!-- 点击事件 -->
<button v-on:click="handleClick">点击我</button>
<button @click="handleClick">简写形式</button>
2. 直接编写简单逻辑
<button @click="count += 1">计数:{{ count }}</button>
二、方法调用
1. 调用组件方法
<button @click="sayHello">打招呼</button>
export default {
methods: {
sayHello() {
alert('Hello Vue!')
}
}
}
2. 传递参数
<button @click="saySomething('你好')">说中文</button>
<button @click="saySomething('Bonjour')">说法语</button>
export default {
methods: {
saySomething(message) {
alert(message)
}
}
}
三、事件修饰符
Vue 提供事件修饰符来处理 DOM 事件的细节。
1. 常用修饰符
修饰符 | 作用 |
---|---|
.stop | 阻止事件冒泡 |
.prevent | 阻止默认行为 |
.capture | 使用事件捕获模式 |
.self | 仅当事件在元素自身触发时生效 |
.once | 事件只触发一次 |
.passive | 提升滚动性能 |
2. 使用示例
<!-- 阻止表单默认提交 -->
<form @submit.prevent="onSubmit">
<button type="submit">提交</button>
</form>
<!-- 阻止点击事件冒泡 -->
<div @click="parentClick">
<button @click.stop="childClick">子按钮</button>
</div>
<!-- 按键修饰符 -->
<input @keyup.enter="submitForm" placeholder="按回车提交">
四、事件对象
1. 获取原生事件对象
<button @click="handleEvent">获取事件对象</button>
export default {
methods: {
handleEvent(event) {
console.log(event.target) // 获取触发元素
}
}
}
2. 同时传递参数和事件对象
<button @click="handleArgs('参数', $event)">传递参数和事件</button>
export default {
methods: {
handleArgs(arg, event) {
console.log(arg, event.clientX)
}
}
}
五、动态事件名
通过变量动态绑定事件类型:
<button @[eventName]="handleDynamicEvent">动态事件</button>
export default {
data() {
return {
eventName: 'click' // 可动态改为 'mouseover' 等
}
},
methods: {
handleDynamicEvent() {
console.log('动态事件触发')
}
}
}
六、高级技巧
1. 多个修饰符链式调用
<a @click.stop.prevent="doSomething">同时阻止冒泡和默认行为</a>
2. 系统修饰键
<!-- Ctrl + 点击 -->
<div @click.ctrl="handleCtrlClick">Ctrl + 点击</div>
<!-- Alt + Enter -->
<input @keyup.alt.enter="clearInput">
3. 鼠标按键修饰符
<button @click.left="leftClick">左键</button>
<button @click.right="rightClick">右键</button>
04 双向数据绑定
一、什么是双向数据绑定?
• 核心概念 :数据变化自动更新视图,视图变化同步更新数据。
• 单向 vs 双向 :
• 单向绑定:
{ { data }}
或
v-bind
(数据 → 视图)
• 双向绑定:
v-model
(数据 ↔ 视图)
• 单向绑定 :像公告栏(数据变 → 页面变,但页面不能改数据)
<div>{{ news }}</div> <!-- 只能显示,不能修改 -->
• 双向绑定 :像可编辑的共享文档(双方都能改,实时同步)
<input v-model="docContent"> <!-- 既能显示,又能修改 -->
• 典型场景 :表单输入(如文本框、复选框、下拉框)
二、v-model 基础用法
1. 文本输入
<input type="text" v-model="message">
<p>输入内容:{{ message }}</p>
2. 多行文本
<textarea v-model="content"></textarea>
3. 复选框
<!-- 单个复选框(布尔值) -->
<input type="checkbox" v-model="isAgree"> 同意协议
<!-- 多个复选框(数组) -->
<input type="checkbox" value="vue" v-model="skills"> Vue
<input type="checkbox" value="react" v-model="skills"> React
4. 单选按钮
<input type="radio" value="male" v-model="gender"> 男
<input type="radio" value="female" v-model="gender"> 女
5. 下拉框
<select v-model="selectedCity">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
三、v-model 原理
v-model
本质是语法糖,等价于:
<input
:value="message"
@input="message = $event.target.value"
>
四、修饰符
1. .lazy
- 转为 change 事件更新
<input v-model.lazy="message"> <!-- 在 input 失焦时更新 -->
2. .number
- 输入转为数字类型
<input v-model.number="age" type="number">
3. .trim
- 自动去除首尾空格
<input v-model.trim="username">
五、高级用法
1. 绑定对象属性
<input v-model="formData.username">
<input v-model="formData.password">
data() {
return {
formData: {
username: '',
password: ''
}
}
}
2. 结合计算属性
export default {
data() {
return { rawInput: '' }
},
computed: {
formattedInput: {
get() {
return this.rawInput.toUpperCase()
},
set(value) {
this.rawInput = value.toLowerCase()
}
}
}
}
<input v-model="formattedInput">
05 组件
1. 组件是什么?
•
类比
:组件就像是一个
自定义的 HTML 标签
,比如你可以创建一个
<my-button>
标签,用来表示一个特定样式的按钮。
• 组成 :每个组件包含:
• HTML 结构 (模板):决定组件长什么样。
• JavaScript 逻辑 (脚本):控制组件的行为(比如点击按钮后做什么)。
• CSS 样式 (样式):定义组件的外观(支持隔离作用域,避免样式冲突)。
2. 为什么要用组件?
• 复用性 :比如你写了一个“登录按钮”组件,可以在多个页面重复使用。
• 维护性 :把页面拆分成多个组件,修改时只需改对应的组件,不会影响其他部分。
• 协作性 :多人开发时,每个人负责不同的组件,最后拼装起来。
3. 创建一个最简单的组件
步骤 1:定义组件
在 Vue 中,组件通常写在
.vue
文件中(单文件组件),比如
MyButton.vue
:
<template>
<!-- HTML 结构 -->
<button class="my-btn" @click="handleClick">
{{ buttonText }}
</button>
</template>
<script>
export default {
// 数据:控制按钮显示的文字
data() {
return {
buttonText: "点我!"
};
},
// 方法:点击按钮后的逻辑
methods: {
handleClick() {
alert("按钮被点击了!");
}
}
};
</script>
<style scoped> /* scoped 表示样式只在当前组件生效 */
.my-btn {
background: blue;
color: white;
padding: 10px 20px;
}
</style>
步骤 2:在父组件中使用它
假设父组件是
App.vue
,你需要先导入
MyButton
,再像普通标签一样使用:
<template>
<div>
<h1>我的第一个组件</h1>
<!-- 使用自定义的按钮组件 -->
<MyButton />
<MyButton /> <!-- 可以重复使用 -->
</div>
</template>
<script>
import MyButton from './MyButton.vue'; // 导入组件
export default {
components: { MyButton } // 注册组件
};
</script>
4. 组件的关键特性
(1) 组件通信:父传子数据(Props)
•
父组件
通过
props
向子组件传递数据:
<!-- 父组件 App.vue -->
<template>
<MyButton :text="parentText" /> <!-- 传递 text 属性 -->
</template>
<script>
export default {
data() {
return { parentText: "父组件传来的文字" };
}
};
</script>
<!-- 子组件 MyButton.vue -->
<template>
<button>{{ text }}</button> <!-- 使用 props -->
</template>
<script>
export default {
props: ['text'] // 声明接收的 props
};
</script>
(2) 组件通信:子传父事件($emit)
•
子组件
通过
$emit
触发事件,
父组件
通过
@事件名
监听:
<!-- 子组件 MyButton.vue -->
<button @click="$emit('btn-click', '传递的数据')">按钮</button>
<!-- 父组件 App.vue -->
<template>
<MyButton @btn-click="handleClick" />
</template>
<script>
export default {
methods: {
handleClick(data) {
console.log("子组件传来的数据:", data);
}
}
};
</script>
(3) 插槽(Slot)
• 让父组件可以向子组件插入内容:
<!-- 子组件 MyCard.vue -->
<div class="card">
<slot>默认内容(如果父组件不传内容,显示这里)</slot>
</div>
<!-- 父组件 App.vue -->
<MyCard>
<h2>这是插入的内容</h2>
<p>会替换子组件的 slot 标签</p>
</MyCard>
5. 组件的注册方式
• 全局注册 :在任何地方都能使用(适合通用组件,如按钮、输入框):
// main.js
import MyButton from './MyButton.vue';
Vue.component('MyButton', MyButton); // 全局注册
• 局部注册 :只在当前组件内使用(适合特定页面组件):
<!-- App.vue -->
<script>
import MyButton from './MyButton.vue';
export default {
components: { MyButton } // 局部注册
};
</script>
6. 常见问题
•
组件命名
:推荐用大写字母开头(如
MyComponent
),使用时可用
<my-component>
。
•
数据隔离
:每个组件的数据(
data
)是独立的,互不影响。
•
样式冲突
:用
<style scoped>
避免组件样式污染全局。
总结
• 组件是 Vue 的核心 ,像积木一样拼装页面。
• 关键三步 :创建组件 → 注册组件 → 使用组件。
•
通信方式
:父传子用
props
,子传父用
$emit
。
06 父子组件通信
在 Vue 中, 父子组件通信 是最常见的场景,比如父组件传递数据给子组件(如参数),或者子组件通知父组件发生了某些事件(如按钮点击)。
一、父传子:通过 props
1. 核心机制
• 父组件 通过 属性(props) 向子组件传递数据。
•
子组件
需要声明接收的
props
,类似接收快递包裹。
2. 代码示例
<!-- 父组件 Parent.vue -->
<template>
<div>
<!-- 传递静态值或动态绑定 -->
<ChildComponent :message="parentMessage" :count="100" />
</div>
</template>
<script>
import ChildComponent from './Child.vue';
export default {
components: { ChildComponent },
data() {
return {
parentMessage: "Hello from Parent!"
};
}
};
</script>
<!-- 子组件 Child.vue -->
<template>
<div>
<!-- 使用 props -->
<p>父组件消息:{{ message }}</p>
<p>父组件给的数字:{{ count }}</p>
</div>
</template>
<script>
export default {
// 声明接收的 props(推荐用对象形式指定类型和默认值)
props: {
message: {
type: String, // 类型校验
required: true // 必传
},
count: {
type: Number,
default: 0 // 默认值
}
}
};
</script>
3. 注意事项
•
单向数据流
:子组件不能直接修改
props
(否则会警告),若需要修改,应在子组件中用
data
或
computed
接收。
•
动态传递
:用
v-bind
(或简写
:
)传递动态数据,如
:count="parentCount"
。
二、子传父:通过 $emit
1. 核心机制
•
子组件
通过
$emit
触发自定义事件
,类似打电话通知父组件。
•
父组件
通过
v-on
(或简写
@
)
监听事件
并处理。
2. 代码示例
<!-- 子组件 Child.vue -->
<template>
<button @click="handleClick">点我通知父组件</button>
</template>
<script>
export default {
methods: {
handleClick() {
// 触发自定义事件,并传递数据
this.$emit('child-click', { data: "子组件的数据" });
}
}
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<div>
<!-- 监听子组件触发的事件 -->
<ChildComponent @child-click="handleChildClick" />
</div>
</template>
<script>
import ChildComponent from './Child.vue';
export default {
components: { ChildComponent },
methods: {
// 接收子组件传递的数据
handleChildClick(payload) {
console.log("收到子组件的消息:", payload.data); // 输出:收到子组件的消息:子组件的数据
}
}
};
</script>
3. 注意事项
•
事件命名
:推荐使用
kebab-case
(如
child-click
),因为 HTML 不区分大小写。
•
参数传递
:
$emit
的第二个参数可以是任意类型数据(如对象、数组)。
三、简化写法: v-model
与 .sync
1. v-model
(双向绑定简化版)
• 适用于表单类组件,如自定义输入框。
•
本质
:是
:value
+
@input
的语法糖。
<!-- 父组件 -->
<ChildComponent v-model="parentValue" />
<!-- 等价于 -->
<ChildComponent :value="parentValue" @input="parentValue = $event" />
<!-- 子组件 -->
<template>
<input :value="value" @input="$emit('input', $event.target.value)" />
</template>
<script>
export default {
props: ['value'] // 必须接收名为 value 的 prop
};
</script>
2. .sync
修饰符
• 用于简化父子组件间的“双向绑定”(Vue 2.3+ 支持)。
•
本质
:是
:propName
+
@update:propName
的语法糖。
<!-- 父组件 -->
<ChildComponent :title.sync="parentTitle" />
<!-- 等价于 -->
<ChildComponent :title="parentTitle" @update:title="parentTitle = $event" />
<!-- 子组件 -->
<button @click="$emit('update:title', '新标题')">修改标题</button>
四、父子通信示意图
父组件 → 子组件:通过 props(单向)
子组件 → 父组件:通过 $emit 事件(触发父组件方法)
五、常见问题
1. 为什么子组件不能直接修改 props?
• 为了遵循 单向数据流 ,保证数据来源清晰可追踪。若需要修改,应该:
•
子组件内部
用
data
接收 props 的初始值:
javascript data() { return { localData: this.parentData }; }
• 或通过
$emit
让父组件修改原始数据。
2. 如何传递复杂数据(如对象、数组)?
• 直接传递即可,Vue 会自动检测对象内部变化(需注意引用类型的特性)。
总结
•
父传子
:用
props
,子组件声明接收。
•
子传父
:用
$emit
,父组件监听事件。
•
简化双向绑定
:
v-model
用于表单,
.sync
用于其他场景。
• 保持数据流的清晰性,是 Vue 组件化开发的关键!
07 axios
Axios 是一个基于 Promise 的 HTTP 客户端库,专门用于浏览器和 Node.js 中发送 HTTP 请求。它简单易用,支持异步请求、拦截请求和响应、自动转换 JSON 数据等功能,是前端开发中与后端 API 交互的常用工具。
一、Axios 的核心优势
- 简单语法
:链式调用,类似
fetch
但更简洁。 - 自动转换数据 :自动将响应数据解析为 JSON,无需手动处理。
- 拦截器 :可以在请求发送前或响应返回后统一处理逻辑(如添加 Token、处理错误)。
- 取消请求 :支持取消未完成的请求(比如用户频繁点击时)。
二、安装 Axios
1. 通过 npm 安装(推荐):
npm install axios
2. 通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
三、基础用法示例
1. 发送 GET 请求
// 语法:axios.get(url, [配置参数])
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data); // 响应数据
})
.catch(error => {
console.error('请求失败:', error);
});
2. 发送 POST 请求
// 语法:axios.post(url, 请求体数据, [配置参数])
axios.post('https://api.example.com/users', {
name: 'John',
age: 30
})
.then(response => {
console.log('创建成功:', response.data);
})
.catch(error => {
console.error('创建失败:', error);
});
3. 发送其他请求(PUT、DELETE)
// PUT 请求(更新数据)
axios.put('https://api.example.com/users/1', {
name: 'Alice'
});
// DELETE 请求(删除数据)
axios.delete('https://api.example.com/users/1');
四、配置参数详解
Axios 支持全局配置和请求级别的配置:
1. 全局默认配置
axios.defaults.baseURL = 'https://api.example.com'; // 统一设置根路径
axios.defaults.headers.common['Authorization'] = 'Bearer token123'; // 统一添加请求头
2. 单个请求配置
axios.get('/data', {
params: { id: 1 }, // URL 参数(自动转为 ?id=1)
headers: { 'X-Custom-Header': 'value' }, // 自定义请求头
timeout: 5000 // 超时时间(毫秒)
});
五、错误处理
Axios 的错误分为两种:
- 请求成功,但 HTTP 状态码非 2xx (如 404、500)。
- 请求本身失败 (如网络错误、跨域问题)。
axios.get('/data')
.then(response => {
// 处理成功响应
})
.catch(error => {
if (error.response) {
// 请求已发送,服务器返回了错误状态码(如 404)
console.log('状态码:', error.response.status);
console.log('错误数据:', error.response.data);
} else if (error.request) {
// 请求已发送,但未收到响应(如网络断开)
console.log('未收到响应:', error.request);
} else {
// 其他错误(如配置错误)
console.log('错误信息:', error.message);
}
});
六、拦截器(高级用法)
拦截器可以在请求或响应被处理前统一处理逻辑:
1. 请求拦截器
axios.interceptors.request.use(config => {
// 在发送请求前添加 Token
config.headers.Authorization = 'Bearer token123';
return config;
}, error => {
return Promise.reject(error);
});
2. 响应拦截器
axios.interceptors.response.use(response => {
// 对响应数据统一处理(如提取 data 字段)
return response.data;
}, error => {
// 统一处理错误
alert('请求失败,请重试!');
return Promise.reject(error);
});
七、在 Vue 项目中使用 Axios
1. 在组件中直接使用
<script>
import axios from 'axios';
export default {
data() {
return {
users: []
};
},
created() {
axios.get('https://api.example.com/users')
.then(response => {
this.users = response.data;
})
.catch(error => {
console.error(error);
});
}
};
</script>
2. 全局挂载(推荐)
在
main.js
中全局引入:
import axios from 'axios';
// 全局配置
axios.defaults.baseURL = 'https://api.example.com';
// 挂载到 Vue 原型(Vue2 用法)
Vue.prototype.$axios = axios;
// 在组件中直接使用
this.$axios.get('/users').then(...);
八、常见问题
1. 跨域问题(CORS)
• 跨域是浏览器的安全限制,需后端配置
Access-Control-Allow-Origin
。
• 开发环境可通过代理解决(如 Vue CLI 的
proxy
配置)。
2. 处理文件上传
const formData = new FormData();
formData.append('file', file); // file 是 input[type=file] 的文件对象
axios.post('/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
总结
Axios 是前端与后端交互的利器,掌握它的基本用法和配置,能让你高效处理 HTTP 请求。初学时重点关注:
- GET/POST 请求的发送 。
- 错误处理 (避免程序崩溃)。
- 拦截器的简单使用 (如添加 Token)。
08 axios和ajax的区别
Axios 和 Ajax 的核心区别
Axios 和 Ajax 都用于前端与服务器的异步数据交互,但它们在实现方式、功能和使用体验上有显著差异:
1. 定义与背景
• Ajax (Asynchronous JavaScript and XML)
•
概念
:一种技术统称,基于
XMLHttpRequest
对象实现异步请求,无需刷新页面即可更新数据。
• 诞生时间 :2005 年(历史悠久,兼容性广泛)。
• Axios
•
概念
:一个基于 Promise 的
第三方 HTTP 客户端库
,封装了
XMLHttpRequest
,提供更简洁的 API。
• 诞生时间 :2014 年后(现代开发常用工具)。
2. 核心实现方式
特性 | Ajax | Axios |
---|---|---|
底层依赖 | 原生 XMLHttpRequest 对象 | 封装了 XMLHttpRequest |
语法风格 | 回调函数(Callback Hell 风险) | Promise/Async Await(链式调用,代码更清晰) |
代码复杂度 | 代码冗长,需手动处理更多细节 | 开箱即用,API 简洁 |
JSON 数据处理 | 需手动解析 JSON.parse() | 自动转换 JSON 数据 |
3. 功能特性对比
• Ajax 的局限性
• 需要自行处理跨浏览器兼容性(如旧版 IE)。
• 需手动设置请求头、转换数据格式。
• 错误处理分散,易产生回调地狱。
• Axios 的优势
• 拦截器 :统一处理请求和响应(如添加 Token、日志监控)。
•
取消请求
:通过
CancelToken
中止未完成的请求。
•
并发处理
:支持
axios.all()
同时发送多个请求。
• 浏览器兼容 :自动适配新旧浏览器(包括 IE11)。
4. 代码示例
原生 Ajax 实现 GET 请求
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText); // 手动解析 JSON
console.log(data);
} else if (xhr.status !== 200) {
console.error('请求失败:', xhr.status);
}
};
xhr.send();
Axios 实现 GET 请求
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data); // 自动解析 JSON
})
.catch(error => {
console.error('请求失败:', error);
});
5. 错误处理差异
•
Ajax
:需在回调中检查
status
和
readyState
,错误处理分散。
•
Axios
:通过
.catch()
集中捕获错误,且能区分请求错误和响应错误:
axios.get('/data')
.catch(error => {
if (error.response) {
console.log('服务器返回错误状态码:', error.response.status);
} else if (error.request) {
console.log('请求已发送但无响应:', error.request);
} else {
console.log('配置错误:', error.message);
}
});
09 计算属性
一、计算属性是什么?
• 定义 :计算属性是 基于响应式依赖动态计算的值 ,它会根据依赖的数据自动更新结果。
• 核心特点 :
• 缓存机制 :依赖的数据未变化时,直接返回缓存值,避免重复计算。
• 声明式逻辑 :将复杂逻辑封装为属性,模板中可像普通数据一样使用。
• 适用场景 :
• 需要根据已有数据动态计算的场景(如:购物车总价、数据过滤)。
• 模板中需要简化复杂表达式时。
二、计算属性 vs 方法
对比维度 | 计算属性(Computed) | 方法(Methods) |
---|---|---|
触发时机 | 依赖数据变化时重新计算 | 每次调用时重新执行 |
缓存 | 有缓存(依赖不变时复用结果) | 无缓存(每次调用重新计算) |
模板中使用方式 | 作为属性使用(无需括号) | 作为方法调用(需括号) |
性能 | 适合频繁计算的复杂逻辑(高效) | 适合不频繁或需要参数的场景 |
三、Vue.js 中的计算属性示例
1. 基础用法
export default {
data() {
return {
price: 100,
quantity: 5
};
},
computed: {
// 计算总价(依赖 price 和 quantity)
total() {
return this.price * this.quantity;
}
}
};
在模板中直接使用:
<p>总价:{{ total }}</p> <!-- 输出:500 -->
2. 复杂逻辑封装
computed: {
// 过滤出价格大于 50 的商品
filteredProducts() {
return this.products.filter(product => product.price > 50);
}
}
四、计算属性的缓存机制
•
缓存生效条件
:只有当依赖的响应式数据(如
price
、
quantity
)变化时,才会重新计算。
• 手动跳过缓存 :若需每次都重新计算,应改用 方法(Methods) 。
五、计算属性的 Setter(高级用法)
计算属性默认只有
getter
,但也可手动定义
setter
:
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName;
},
set(newValue) {
// 当修改 fullName 时,拆分并更新 firstName 和 lastName
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1] || '';
}
}
}
使用示例:
this.fullName = 'John Doe'; // 自动触发 setter
六、代码实战练习
// 示例:根据用户输入过滤列表
data() {
return {
searchText: '',
products: [
{ name: 'Apple', price: 10 },
{ name: 'Banana', price: 5 },
{ name: 'Orange', price: 8 }
]
};
},
computed: {
filteredProducts() {
return this.products.filter(product =>
product.name.toLowerCase().includes(this.searchText.toLowerCase())
);
}
}
模板中使用:
<input v-model="searchText" placeholder="搜索商品">
<ul>
<li v-for="product in filteredProducts" :key="product.name">
{{ product.name }} - ¥{{ product.price }}
</li>
</ul>
10 监听器
侦听器(Watcher) 是前端框架(如 Vue.js)中用于监听数据变化并执行特定逻辑的功能,尤其适合处理异步操作或复杂数据变更场景。
一、侦听器的核心概念
• 作用 :监听某个数据的变化,并在变化时触发回调函数。
• 适用场景 :
• 数据变化后需执行异步操作(如 API 请求)。
• 数据变化后需执行复杂逻辑(如多个步骤处理)。
• 需要观察数据变化的具体过程(如旧值和新值对比)。
二、Vue.js 中的侦听器示例
1. 基础用法
export default {
data() {
return {
count: 0,
userInput: ''
};
},
watch: {
// 监听 count 的变化
count(newVal, oldVal) {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
},
// 监听用户输入的变化(用于搜索联想)
userInput(newVal) {
this.fetchSearchResults(newVal); // 调用异步方法
}
},
methods: {
fetchSearchResults(query) {
// 模拟 API 请求
setTimeout(() => {
console.log('搜索:', query);
}, 300);
}
}
};
三、侦听器的配置选项
通过对象语法,可配置
handler
函数及监听策略:
watch: {
dataToWatch: {
handler(newVal, oldVal) { /* 逻辑 */ },
deep: true, // 深度监听(对象内部变化)
immediate: true // 立即触发(初始化时执行一次)
}
}
1. 深度监听(deep)
用于监听对象或数组内部属性的变化:
data() {
return {
user: { name: 'Alice', age: 25 }
};
},
watch: {
user: {
handler(newVal) {
console.log('用户信息变化:', newVal);
},
deep: true // 监听 user 对象所有属性的变化
}
}
2. 立即执行(immediate)
初始化时立即触发一次侦听逻辑:
watch: {
count: {
handler(newVal) {
console.log('初始值:', newVal); // 初始化时打印 count 的初始值
},
immediate: true
}
}
四、经典使用场景
1. 表单输入实时校验
watch: {
email(newVal) {
if (!this.validateEmail(newVal)) {
this.error = '邮箱格式错误';
} else {
this.error = '';
}
}
}
2. 路由参数变化重新加载数据
watch: {
'$route.params.id'(newId) {
this.fetchData(newId); // 路由变化时重新请求数据
}
}
3. 监听对象属性变化(需深度监听)
watch: {
'formData.username': {
handler(newVal) {
console.log('用户名变化:', newVal);
}
}
}
11 计算属性和侦听器的区别
计算属性(Computed Properties)与侦听器(Watchers)的区别
对比维度 | 计算属性(Computed) | 侦听器(Watchers) |
---|---|---|
核心目的 | 动态计算并返回新值 ,用于模板展示或依赖其他数据的场景。 | 响应数据变化 ,执行副作用(如异步操作、复杂逻辑)。 |
缓存机制 | 有缓存 :依赖数据未变化时直接返回缓存结果,避免重复计算。 | 无缓存 :每次数据变化均触发回调。 |
语法定义 | 在 computed 选项中定义,返回计算结果。 | 在 watch 选项中定义,指定监听的数据及回调函数。 |
依赖追踪 | 自动追踪依赖 :根据函数内使用的响应式数据自动更新。 | 手动指定依赖 :需明确监听特定数据或路径。 |
返回值 | 必须返回一个值,供模板或逻辑使用。 | 无需返回值,侧重执行操作。 |
适用场景 | 1. 模板中需要动态计算的表达式(如拼接字符串、过滤列表)。 2. 依赖多个数据的同步计算(如购物车总价)。 | 1. 数据变化后需执行异步任务(如API请求)。 2. 需要旧值和新值对比的场景(如记录变化历史)。 3. 监听非响应式数据或复杂对象内部变化(需 deep: true )。 |
性能优化 | 高效 :依赖不变时直接使用缓存,适合频繁计算的场景。 | 需谨慎使用 :频繁变化的数据可能引发多次回调,需防抖或节流。 |
核心区别解析
设计目的不同
• 计算属性 :核心是 派生新数据 。例如,将
firstName
和lastName
组合成fullName
。• 侦听器 :核心是 观察数据变化后的动作 。例如,用户输入搜索关键词时自动发起API请求。
响应方式不同
• 计算属性 :自动追踪函数内部依赖的响应式数据,依赖变化时重新计算。
• 侦听器 :需手动指定监听的目标数据,且支持深度监听(
deep: true
)或立即触发(immediate: true
)。缓存机制的影响
• 计算属性 :多次访问同一计算属性时,只要依赖未变化,直接返回缓存值,避免重复计算。
• 侦听器 :每次数据变化均触发回调,适合需要实时响应的场景(如输入框校验)。
何时使用计算属性?
• 场景1 :需要基于现有数据动态生成新值。
computed: {
totalPrice() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
• 场景2 :模板中有复杂表达式需简化。
<!-- 模板中直接使用计算属性 -->
<p>{{ fullName }}</p>
何时使用侦听器?
• 场景1 :数据变化后需执行异步操作。
watch: {
searchText(newVal) {
this.debouncedFetchResults(newVal); // 防抖后请求
}
}
• 场景2 :需要深度监听对象或数组内部变化。
watch: {
user: {
handler(newVal) {
console.log('用户信息变化:', newVal);
},
deep: true // 监听 user 对象所有属性
}
}
总结
• 计算属性 :用于 同步计算派生数据 ,优先使用,提升代码可读性和性能。
• 侦听器 :用于 数据变化后的副作用操作 (如异步、DOM操作),需谨慎避免滥用。
12 插槽
插槽是什么?
想象你有一个 手机壳 ,壳子上预留了摄像头和按钮的孔位。
插槽 就像这些孔位——壳子(子组件)定义好结构,你(父组件)可以按需要插入不同的摄像头模组或按钮(自定义内容)。
为什么需要插槽?
插槽 就是解决这个问题的: 壳子固定,内容随便换 !
插槽的三种用法
1. 默认插槽(万能孔)
• 子组件 :预留一个“万能孔”,不指定内容时显示默认值。
<!-- 子组件:Card.vue -->
<div class="card">
<slot>默认提示文字</slot> <!-- 这里是插槽位置 -->
</div>
• 父组件 :往孔里塞任意内容(替换默认文字)。
<Card>
<img src="cat.jpg"> <!-- 插入图片 -->
</Card>
2. 具名插槽(多个孔,对号入座)
• 子组件 :定义多个带名字的孔(比如头部、内容、底部)。
<!-- 子组件:Layout.vue -->
<div class="layout">
<slot name="header"></slot> <!-- 头部孔 -->
<slot name="content"></slot> <!-- 内容孔 -->
<slot name="footer"></slot> <!-- 底部孔 -->
</div>
• 父组件 :把内容精准插入对应孔。
<Layout>
<template #header><h1>我是标题</h1></template>
<template #content><p>我是正文...</p></template>
<template #footer><button>提交</button></template>
</Layout>
3. 作用域插槽(带数据的孔)
• 子组件 :往孔里传递数据,父组件决定怎么用。
<!-- 子组件:DataList.vue -->
<ul>
<li v-for="item in items">
<slot :item="item">默认 {{ item.name }}</slot>
</li>
</ul>
• 父组件 :拿到数据,自定义显示方式。
<DataList :items="products">
<template #default="{ item }"> <!-- 解构数据 -->
{{ item.name }} - ¥{{ item.price }}
</template>
</DataList>
插槽 vs Props
• Props :传递数据(比如数字、字符串),子组件自己决定怎么显示。
• 插槽 :传递内容(比如HTML、组件),父组件完全控制显示方式。
总结:什么时候用插槽?
就像 玩具的电池槽 :玩具(子组件)提供槽位和电力,你(父组件)决定用 5 号电池还是充电电池!
13 自定义事件内容分发
自定义事件的内容分发是一种在前端框架(如 Vue.js、React 等)中实现组件间通信的常见模式,尤其在父子组件或跨组件数据传递时非常有用。其核心思想是 子组件通过触发自定义事件,将数据或行为传递给父组件或其他监听者 ,从而实现解耦和复用。
核心概念
自定义事件
子组件可以定义并触发自己的事件(例如
update
、submit
),父组件监听这些事件并执行对应的逻辑。内容分发
通常指通过插槽(Slot)将父组件的内容传递到子组件的特定位置,但结合自定义事件后,可以实现更灵活的数据交互。
步骤 1: 子组件定义并触发事件
子组件通过
$emit
方法触发自定义事件,并向父组件传递数据。
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<!-- 触发自定义事件 -->
<button @click="notifyParent">向父组件发送数据</button>
</div>
</template>
<script>
export default {
methods: {
notifyParent() {
// 触发名为 'custom-event' 的事件,并传递数据
this.$emit('custom-event', { data: '来自子组件的数据' });
}
}
}
</script>
步骤 2: 父组件监听并处理事件
父组件通过
v-on
(或
@
简写)监听子组件触发的自定义事件,并绑定处理函数。
<!-- 父组件 ParentComponent.vue -->
<template>
<div>
<!-- 监听子组件的自定义事件 -->
<child-component @custom-event="handleCustomEvent" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
methods: {
// 处理子组件传递的数据
handleCustomEvent(payload) {
console.log('接收到子组件数据:', payload.data); // 输出:来自子组件的数据
}
}
}
</script>
步骤 3: 结合插槽实现内容分发
通过插槽(Slot)将父组件的内容分发到子组件,并利用作用域插槽传递子组件的数据。
子组件(定义插槽并传递数据)
<!-- 子组件 SlotComponent.vue -->
<template>
<div>
<!-- 定义作用域插槽,暴露数据给父组件 -->
<slot :item="item" :onClick="handleClick"></slot>
</div>
</template>
<script>
export default {
data() {
return { item: { id: 1, name: '示例数据' } };
},
methods: {
handleClick() {
// 触发自定义事件
this.$emit('item-clicked', this.item);
}
}
}
</script>
父组件(使用插槽并绑定事件)
<!-- 父组件 ParentComponent.vue -->
<template>
<slot-component>
<!-- 接收子组件传递的作用域数据和方法 -->
<template v-slot:default="slotProps">
<button @click="slotProps.onClick">
{{ slotProps.item.name }}
</button>
</template>
</slot-component>
</template>
<script>
import SlotComponent from './SlotComponent.vue';
export default {
components: { SlotComponent },
methods: {
// 监听子组件触发的自定义事件
handleItemClicked(item) {
console.log('点击的项:', item.name); // 输出:示例数据
}
}
}
</script>
步骤 4: 跨组件通信(非父子)
对于非父子组件,可通过 全局事件总线 或状态管理工具(如 Vuex)实现通信。
事件总线示例
// 创建事件总线(Event Bus)
// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
// 组件 A(触发事件)
EventBus.$emit('global-event', { data: '跨组件数据' });
// 组件 B(监听事件)
EventBus.$on('global-event', (payload) => {
console.log(payload.data); // 输出:跨组件数据
});
关键步骤总结
子组件触发事件
使用
this.$emit('事件名', 数据)
发送数据。父组件监听事件
通过
@事件名="处理方法"
接收并处理数据。插槽分发内容
结合作用域插槽(
v-slot
)传递子组件数据和方法。跨组件通信
使用全局事件总线或状态管理工具。
注意事项
事件命名规范
使用 kebab-case (如
update-data
),避免与原生事件(如click
)冲突。单向数据流
子组件不要直接修改父组件传递的
props
,应通过事件通知父组件修改。解耦设计
子组件只负责触发事件,具体逻辑由父组件或其他监听者处理。
14 vue-cli
Vue CLI 是 Vue.js 的 ,用于快速搭建 Vue 项目、管理项目配置和集成开发工具链。
一、Vue CLI 的核心功能
标准化项目模板
提供预配置的 Webpack、Babel、ESLint 等工具链,开箱即用。
图形化项目管理
支持通过
vue ui
命令启动可视化界面管理项目。插件系统
可扩展功能(如添加 TypeScript、Vuex、Router 等)。
环境变量管理
支持开发、测试、生产环境的变量配置。
二、安装与使用步骤
1. 安装 Vue CLI
# 全局安装(需要 Node.js >= 8.9)
npm install -g @vue/cli
# 或使用 yarn
yarn global add @vue/cli
# 验证安装
vue --version
2. 创建新项目
# 交互式创建项目
vue create my-project
# 选择预设(默认或手动配置)
? Please pick a preset:
Default ([Vue 2] babel, eslint)
Default (Vue 3) ([Vue 3] babel, eslint)
Manually select features # 手动选择特性(推荐)
3. 手动配置选项(常见选择)
• Babel :ES6+ 语法转换
• TypeScript :支持 TS
• Router :Vue 路由
• Vuex :状态管理
• CSS Pre-processors :Sass/Less
• Linter/Formatter :代码规范检查
• Unit Testing :单元测试
4. 启动开发服务器
cd my-project
npm run serve
# 访问 http://localhost:8080
三、项目目录结构
my-project/
├── node_modules/ # 依赖包
├── public/ # 静态资源(直接复制到输出目录)
│ ├── index.html # 主 HTML 文件
│ └── favicon.ico
├── src/ # 源码目录
│ ├── assets/ # 图片、字体等资源
│ ├── components/ # 组件
│ ├── views/ # 页面级组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── App.vue # 根组件
│ └── main.js # 入口文件
├── .eslintrc.js # ESLint 配置
├── babel.config.js # Babel 配置
└── package.json # 项目依赖和脚本
四、常用命令
命令 | 作用 |
---|---|
npm run serve | 启动开发服务器 |
npm run build | 打包生产环境代码(输出到 /dist ) |
npm run lint | 运行 ESLint 代码检查 |
npm run test:unit | 运行单元测试 |
vue add plugin-name | 添加插件(如 vue add router ) |
五、Vue CLI 插件系统
1. 添加插件
# 添加 Vue Router
vue add router
# 添加 Vuex
vue add vuex
# 添加 UI 库(如 Element UI)
vue add element-plus
六、环境变量配置
创建环境文件
•
.env.development
:开发环境变量•
.env.production
:生产环境变量定义变量(以
VUE_APP_
开头)# .env.development VUE_APP_API_URL = http://localhost:3000/api
代码中使用
// 通过 process.env 访问 axios.get(process.env.VUE_APP_API_URL);
七、图形化管理界面(Vue UI)
# 启动可视化界面
vue ui
• 功能 :创建项目、安装依赖、运行任务、配置插件等。
• 优势 :适合不熟悉命令行的开发者。
八、Vue CLI vs Vite
特性 | Vue CLI | Vite |
---|---|---|
构建工具 | Webpack | Rollup(开发时用 ESBuild) |
启动速度 | 较慢 | 极快(基于原生 ESM) |
适用场景 | 复杂项目、兼容性要求高 | 现代浏览器、快速开发 |
配置复杂度 | 高 | 低 |
注意 :Vue 3 官方推荐新项目使用 Vite,但 Vue CLI 仍广泛用于企业级项目。
九、升级 Vue CLI
# 更新全局 CLI
npm update -g @vue/cli
# 更新项目本地依赖
npm update @vue/cli-service
十、总结
• 适用场景 :需要标准化配置、企业级项目、兼容旧浏览器。
• 优势 :生态成熟、插件丰富、配置可控。
• 替代方案 :Vite(适合追求速度的现代项目)。
通过 Vue CLI,开发者可以专注于业务逻辑,而无需手动配置复杂的构建工具链。
15 Webpack
学习 Webpack 是现代前端开发的重要技能,它是一款强大的 模块打包工具 ,能将前端资源(JS、CSS、图片等)转换为优化的静态文件。
一、Webpack 核心概念
入口(Entry)
定义打包的起点文件(默认为
./src/index.js
)。出口(Output)
指定打包后的文件输出位置(如
./dist
)。加载器(Loader)
处理非 JS 文件(如 CSS、图片、字体等),将其转换为模块。
插件(Plugin)
扩展功能(如代码压缩、HTML 生成、环境变量注入)。
模式(Mode)
区分开发和生产环境(
development
或production
)。
二、快速上手
1. 初始化项目
mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
2. 基础目录结构
webpack-demo/
├── src/
│ ├── index.js # 入口文件
│ └── utils.js # 工具模块
├── public/ # 静态资源
│ └── index.html # HTML 模板
├── webpack.config.js # Webpack 配置文件
└── package.json
3. 基础配置
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development', // 开发模式
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist'), // 输出路径
},
plugins: [
// 自动生成 HTML 并注入打包后的 JS
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
4. 运行打包
npx webpack --config webpack.config.js
# 或添加到 package.json 脚本
"scripts": {
"build": "webpack"
}
三、核心功能详解
1. 处理 CSS 和 SCSS
安装 Loader:
npm install style-loader css-loader sass-loader sass --save-dev
配置:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /css$/i,
use: ['style-loader', 'css-loader'], // 从右向左执行
},
{
test: /scss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
}
]
}
};
2. 处理图片和字体
安装 Loader:
npm install file-loader url-loader --save-dev
配置:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /(png|jpg|gif|svg)$/i,
type: 'asset/resource', // Webpack 5+ 推荐方式
// 或使用 url-loader(处理小文件转 Base64)
// use: {
// loader: 'url-loader',
// options: { limit: 8192 }
// }
},
{
test: /(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
}
]
}
};
3. 使用 Babel 转译 JS
安装依赖:
npm install babel-loader @babel/core @babel/preset-env --save-dev
配置:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
};
4. 开发环境优化
// webpack.config.js
module.exports = {
mode: 'development',
devtool: 'inline-source-map', // 生成 Source Map
devServer: {
static: './dist', // 开发服务器根目录
hot: true, // 热更新
port: 3000,
},
};
启动开发服务器:
npm install webpack-dev-server --save-dev
# package.json 添加脚本
"scripts": {
"start": "webpack serve --open"
}
四、常用插件
HtmlWebpackPlugin
自动生成 HTML 并注入打包后的 JS/CSS。
MiniCssExtractPlugin
将 CSS 提取为独立文件(生产环境优化)。
CleanWebpackPlugin
每次打包前清空输出目录。
DefinePlugin
定义全局常量(如
process.env.NODE_ENV
)。
安装及配置示例:
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://api.example.com'),
}),
],
};
五、生产环境优化
代码压缩
• JS 压缩:
TerserWebpackPlugin
(Webpack 5+ 默认启用)。• CSS 压缩:
CssMinimizerWebpackPlugin
。代码分割(Code Splitting)
// webpack.config.js module.exports = { optimization: { splitChunks: { chunks: 'all', // 分割公共模块 }, }, };
缓存优化
使用
[contenthash]
生成文件名:output: { filename: '[name].[contenthash].js', }
六、调试与常见问题
查看打包分析
使用
webpack-bundle-analyzer
:npm install webpack-bundle-analyzer --save-dev
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; plugins: [new BundleAnalyzerPlugin()];
路径问题
使用
path.resolve(__dirname, '路径')
处理绝对路径。Loader 顺序错误
Loader 执行顺序为 从后向前 ,例如
['style-loader', 'css-loader']
会先执行css-loader
。
七、学习资源推荐
官方文档
实战教程
•
八、总结
• 核心目标 :将分散的前端资源打包为优化的静态文件。
• 核心能力 :Loader 处理非 JS 文件,Plugin 扩展功能。
• 进阶方向 :性能优化(Tree Shaking、懒加载)、自定义插件开发。
16 vue-router
Vue Router 是 Vue.js 的 官方路由管理库 ,用于构建 单页面应用(SPA) ,实现页面跳转、动态路由、嵌套路由等功能。
一、核心概念
路由(Route)
定义 URL 路径与组件的映射关系。
路由器(Router)
管理所有路由规则和导航行为。
路由视图(RouterView)
用于渲染匹配的组件。
导航守卫(Navigation Guards)
控制路由跳转前后的逻辑(如权限校验)。
二、快速入门
1. 安装
# Vue 2 项目
npm install vue-router@3
# Vue 3 项目
npm install vue-router@4
2. 基础配置(Vue 3 示例)
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
// 1. 定义路由组件
const Home = () => import('./views/Home.vue');
const About = () => import('./views/About.vue');
// 2. 定义路由规则
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
// 3. 创建路由实例
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 历史模式
routes
});
export default router;
3. 挂载到 Vue 应用
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router); // 启用路由
app.mount('#app');
4. 在组件中使用
<!-- App.vue -->
<template>
<div>
<!-- 导航链接 -->
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<!-- 路由出口 -->
<router-view></router-view>
</div>
</template>
三、核心功能详解
1. 动态路由匹配
通过
:
定义动态参数,在组件中通过
$route.params
获取。
// 路由配置
{ path: '/user/:id', component: User }
// 组件内访问参数
this.$route.params.id; // Vue 2
import { useRoute } from 'vue-router';
const route = useRoute(); // Vue 3
console.log(route.params.id);
2. 嵌套路由
使用
children
定义子路由,父组件需包含
<router-view>
。
// 路由配置
{
path: '/dashboard',
component: Dashboard,
children: [
{ path: 'profile', component: Profile }, // /dashboard/profile
{ path: 'settings', component: Settings }
]
}
3. 编程式导航
通过
router.push
或
router.replace
跳转。
// 在组件中(Vue 3 组合式 API)
import { useRouter } from 'vue-router';
const router = useRouter();
// 跳转到指定路径
router.push('/about');
router.push({ path: '/user/1' });
// 替换当前页面(无历史记录)
router.replace('/login');
4. 导航守卫
控制路由跳转逻辑(如登录验证)。
// 全局前置守卫
router.beforeEach((to, from, next) => {
const isAuthenticated = checkLogin(); // 检查是否登录
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login'); // 跳转到登录页
} else {
next(); // 允许通过
}
});
// 路由独享守卫
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => { /* 逻辑 */ }
}
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) { /* 进入前 */ },
beforeRouteUpdate(to, from, next) { /* 路由更新 */ },
beforeRouteLeave(to, from, next) { /* 离开前 */ }
}
5. 路由元信息(Meta Fields)
通过
meta
字段存储额外信息(如页面标题、权限)。
{
path: '/profile',
component: Profile,
meta: { requiresAuth: true, title: '用户中心' }
}
6. 懒加载路由
按需加载组件,优化性能。
// Vue 3 动态导入
const User = () => import('./views/User.vue');
// Webpack 魔法注释(指定 chunk 名称)
const User = () => import(/* webpackChunkName: "user" */ './views/User.vue');
四、高级功能
1. 滚动行为
控制页面切换后的滚动位置。
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition; // 恢复历史滚动位置
} else {
return { top: 0 }; // 滚动到顶部
}
}
});
2. 路由模式
•
Hash 模式
:URL 带
#
,兼容性好。
createWebHashHistory()
• History 模式 :需服务器配置支持。
createWebHistory()
五、常见问题
1. 404 页面处理
{ path: '/:pathMatch(.*)*', component: NotFound }
2. 路由重复跳转报错
在路由跳转时捕获错误:
router.push('/path').catch(err => {});
六、注意事项
路由命名
建议为路由定义
name
属性,便于编程式导航。{ path: '/user', name: 'user', component: User } router.push({ name: 'user' });
路由顺序
动态路由应放在静态路由之后。
性能优化
使用懒加载拆分代码块。
七、总结
Vue Router 是构建 Vue 单页面应用的核心工具,掌握其核心功能(动态路由、守卫、懒加载)和最佳实践,能高效实现复杂路由逻辑。结合 Vuex 状态管理,可构建企业级前端应用。
推荐学习 :
•
17 elemenUI
一、环境准备
1. 创建 Vue 项目并安装依赖
# 创建 Vue 项目(Vue 3)
npm create vue@latest
# 进入项目目录
cd your-project
# 安装 Element Plus(适用于 Vue 3)
npm install element-plus --save
# 安装 Vue Router
npm install vue-router@4
2. 完整引入 Element Plus
// main.js
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')
二、路由配置(Vue Router)
1. 定义路由文件
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入页面组件
const Home = () => import('@/views/Home.vue')
const User = () => import('@/views/User.vue')
const NotFound = () => import('@/views/404.vue')
const routes = [
{ path: '/', redirect: '/home' },
{
path: '/home',
name: 'Home',
component: Home,
meta: { title: '首页', icon: 'House' } // 用于菜单渲染
},
{
path: '/user/:id',
name: 'User',
component: User,
meta: { requiresAuth: true } // 需要登录
},
{ path: '/:pathMatch(.*)*', component: NotFound } // 404 页面
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
三、结合 Element UI 实现布局
1. 使用 Element 的布局组件
<!-- src/App.vue -->
<template>
<el-container>
<!-- 侧边栏 -->
<el-aside width="200px">
<el-menu
router
:default-active="$route.path"
@select="handleMenuSelect"
>
<el-menu-item index="/home">
<el-icon><House /></el-icon>
<span>首页</span>
</el-menu-item>
<el-menu-item index="/user/1">
<el-icon><User /></el-icon>
<span>用户中心</span>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 主内容区 -->
<el-main>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main>
</el-container>
</template>
<script setup>
import { House, User } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const handleMenuSelect = (index) => {
router.push(index)
}
</script>
<style>
.el-aside {
background-color: #304156;
}
.el-menu {
border-right: none;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
四、常见场景实现
1. 表单提交后跳转(Element 表单 + 路由)
<!-- src/views/Login.vue -->
<template>
<el-form :model="form" @submit.prevent="handleSubmit">
<el-form-item label="用户名">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" />
</el-form-item>
<el-button type="primary" native-type="submit">登录</el-button>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
const router = useRouter()
const form = ref({ username: '', password: '' })
const handleSubmit = async () => {
// 模拟登录成功
if (form.value.username && form.value.password) {
ElMessage.success('登录成功!')
router.push('/dashboard')
} else {
ElMessage.error('请填写完整信息')
}
}
</script>
2. 面包屑导航(Element Breadcrumb)
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item
v-for="(route, index) in matchedRoutes"
:key="index"
:to="{ path: route.path }"
>
{{ route.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const matchedRoutes = computed(() => {
return route.matched.filter(r => r.meta?.title)
})
</script>
五、权限控制实现
1. 全局路由守卫 + Element 弹窗
// router/index.js
router.beforeEach((to, from, next) => {
const isAuthenticated = localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
ElMessageBox.confirm('请先登录', '提示', {
confirmButtonText: '去登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
next('/login')
}).catch(() => {
next(false) // 取消导航
})
} else {
next()
}
})
六、优化技巧
图标自动导入
使用
unplugin-icons
自动导入 Element 图标:// vite.config.js import Icons from 'unplugin-icons/vite' export default defineConfig({ plugins: [ Icons({ compiler: 'vue3' }) ] })
按需加载组件
使用
unplugin-vue-components
自动导入 Element 组件:// vite.config.js import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ Components({ resolvers: [ElementPlusResolver()] }) ] })
七、项目结构建议
src/
├── assets/
├── components/
├── router/
├── store/ # Vuex/Pinia 状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/
│ ├── Home.vue
│ ├── User.vue
│ └── Login.vue
└── App.vue
八、常见问题
菜单高亮问题
确保
el-menu
的default-active
绑定当前路由路径::default-active="$route.path"
动态路由刷新白屏
使用
router.onReady()
或在App.vue
中添加key
:<router-view :key="$route.fullPath" />
通过 Vue Router 管理页面导航,结合 快速搭建美观界面,可高效开发后台管理系统、数据看板等中后台项目。推荐结合 Pinia 进行状态管理,使用 Axios 处理接口请求,构建完整的前端解决方案。
18 参数传递和重定向
在 Vue 应用中, 参数传递 和 重定向 是路由管理的核心功能。
一、参数传递的 4 种方式
1. 动态路由参数(Params)
适合传递
必填参数
(如用户ID),通过
:
定义动态路径。
// 路由配置
{
path: '/user/:id',
name: 'User',
component: User
}
// 跳转传递
router.push({ name: 'User', params: { id: 123 } });
// 或
<router-link :to="{ name: 'User', params: { id: 123 }}">用户</router-link>
// 组件内获取
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id); // 123
注意 :
•
params
必须与路由定义的参数名一致
• 使用
name
跳转更可靠(直接
path
跳转可能导致
params
失效)
2. 查询参数(Query)
适合传递 可选参数 (如搜索关键词),参数显示在 URL 中。
// 跳转传递
router.push({ path: '/search', query: { keyword: 'vue' } });
// 或
<router-link :to="{ path: '/search', query: { keyword: 'vue' }}">搜索</router-link>
// 组件内获取
console.log(route.query.keyword); // 'vue'
URL 示例
:
/search?keyword=vue
3. Props 传参
解耦组件与路由,使组件不直接依赖
$route
。
// 路由配置
{
path: '/user/:id',
component: User,
props: true // 将 params 自动转为 props
// 或使用函数模式
// props: (route) => ({ id: route.params.id, query: route.query })
}
// 组件定义
export default {
props: ['id'], // 接收 props
setup(props) {
console.log(props.id);
}
}
4. 状态管理(Vuex/Pinia)
适合 复杂数据 或 跨组件共享 的场景。
// 存储数据
import { useStore } from '@/store';
const store = useStore();
store.setUserInfo({ name: 'John' });
// 跳转
router.push('/profile');
// 目标组件获取
const userInfo = store.userInfo;
二、重定向的 3 种实现方式
1. 全局重定向(路由配置)
在路由规则中使用
redirect
字段:
// 简单重定向
{ path: '/home', redirect: '/' }
// 命名路由重定向
{ path: '/old', redirect: { name: 'Home' } }
// 动态重定向(函数模式)
{
path: '/redirect/:path*',
redirect: (to) => {
return { path: '/' + to.params.path };
}
}
2. 导航守卫重定向
在全局/路由独享守卫中控制跳转:
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next({ path: '/login', query: { redirect: to.fullPath } });
} else {
next();
}
});
// 登录成功后跳回原页面
const redirect = route.query.redirect || '/';
router.push(redirect);
3. 编程式重定向
在组件逻辑中强制跳转:
import { useRouter } from 'vue-router';
const router = useRouter();
// 替换当前历史记录
router.replace('/new-path');
// 带参数跳转
router.push({
path: '/error',
query: { code: 404, message: '页面不存在' }
});
三、特殊场景处理
1. 404 页面配置
{
path: '/:pathMatch(.*)*', // 匹配所有未定义路径
name: 'NotFound',
component: NotFound
}
2. 路由参数类型转换
动态参数默认是字符串,可通过
props
- 转换函数处理类型:
// 路由配置
{
path: '/product/:id(\\d+)', // 限制为数字
component: Product,
props: (route) => ({ id: Number(route.params.id) })
}
3. 保留当前页面查询参数
// 跳转时保留现有查询参数
router.push({ query: { ...route.query, newParam: 'value' } });
四、最佳实践总结
场景 | 推荐方式 |
---|---|
必填参数(如ID) | 动态路由参数 ( params ) |
可选参数(如筛选条件) | 查询参数 ( query ) |
解耦组件与路由 | Props 传参 |
敏感数据(如Token) | Vuex/Pinia 状态管理 |
登录跳转 | 导航守卫重定向 |
页面不存在 | 全局 404 路由 |
五、注意事项
- 避免直接修改
$route
对象 ,应使用路由方法跳转 - 动态路由参数变化时
,需用
beforeRouteUpdate
守卫监听 - Hash vs History 模式 :生产环境需服务器配置支持 History 模式
19 404页面和路由钩子
在 Vue 应用中, 404 页面处理 和**路由钩子(导航守卫)**是实现复杂路由逻辑的关键技术。
一、404 页面处理
1. 配置通配符路由
// router/index.js
{
// 匹配所有未定义路径(Vue Router 4+ 语法)
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/404.vue')
}
2. 404 页面组件示例
<!-- views/404.vue -->
<template>
<div class="not-found">
<h1>404</h1>
<p>您访问的页面不存在</p>
<el-button @click="router.push('/')">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
</script>
3. 注意事项
• 路由顺序 :必须放在路由配置的最后
• 路径匹配语法 :
•
:pathMatch(.*)*
:匹配任意路径并保留嵌套结构
•
:pathMatch(.*)
:匹配任意路径但不保留嵌套
•
服务器配置
:若使用 History 模式,需配置服务器 fallback 到
index.html
二、路由钩子(导航守卫)
路由钩子用于在路由跳转前后执行特定逻辑,分为三类:
1. 全局守卫
// router/index.js
router.beforeEach((to, from, next) => {
// 逻辑 1:修改页面标题
document.title = to.meta.title || '默认标题';
// 逻辑 2:登录验证
if (to.meta.requiresAuth && !isLoggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath } // 记录原路径
});
} else {
next(); // 放行
}
});
router.afterEach((to, from) => {
// 跳转完成后执行(无 next 参数)
logPageView(to.fullPath); // 示例:统计页面访问
});
2. 路由独享守卫
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (!checkAdminPermission()) {
next('/no-permission');
} else {
next();
}
}
}
3. 组件内守卫
// 在组件选项中定义
export default {
beforeRouteEnter(to, from, next) {
// 不能访问组件实例 `this`
next(vm => {
// 通过 vm 访问组件实例
vm.loadData();
});
},
beforeRouteUpdate(to, from, next) {
// 路由变化但组件被复用时触发
this.userId = to.params.id;
next();
},
beforeRouteLeave(to, from, next) {
// 离开前确认未保存更改
if (this.hasUnsavedChanges) {
const answer = confirm('有未保存的更改,确定离开吗?');
answer ? next() : next(false);
} else {
next();
}
}
}
三、进阶使用场景
1. 异步导航守卫
router.beforeEach(async (to, from, next) => {
// 等待异步检查
const hasPermission = await checkPermission(to.meta.role);
hasPermission ? next() : next('/403');
});
2. 动态添加路由
// 添加管理后台路由(需权限)
if (user.isAdmin) {
router.addRoute({
path: '/admin',
component: Admin,
meta: { requiresAuth: true }
});
}
3. 导航取消处理
router.push('/dashboard').catch(error => {
if (error.name === 'NavigationDuplicated') {
// 处理重复导航错误
}
});
四、执行顺序与流程图
1. 完整导航解析流程
1. 导航被触发
2. 调用离开组件的 `beforeRouteLeave`
3. 调用全局 `beforeEach`
4. 调用复用组件的 `beforeRouteUpdate`
5. 调用路由配置的 `beforeEnter`
6. 解析异步路由组件
7. 调用进入组件的 `beforeRouteEnter`
8. 调用全局 `beforeResolve`
9. 导航被确认
10. 调用全局 `afterEach`
11. 触发 DOM 更新
12. 执行 `beforeRouteEnter` 中的回调函数
2. 钩子执行顺序示例
// 假设从 /a 跳转到 /b
beforeRouteLeave (组件A) // 离开当前组件
↓
beforeEach (全局) // 全局前置钩子
↓
beforeEnter (路由B的独享守卫) // 目标路由配置的守卫
↓
beforeRouteEnter (组件B) // 进入目标组件前
↓
afterEach (全局) // 全局后置钩子
五、最佳实践
1. 404 页面使用场景
• 未匹配的路由
• 服务器 404 状态码返回(需 SSR 支持)
• 权限不足时的兜底展示
2. 导航守卫使用场景
钩子类型 | 典型场景 |
---|---|
beforeEach | 全局权限验证、页面标题设置 |
beforeEnter | 特定路由的访问控制(如VIP页面) |
beforeRouteLeave | 阻止用户意外离开表单页 |
afterEach | 页面访问统计、滚动位置复位 |
3. 注意事项
• 避免阻塞逻辑 :导航守卫中不要执行长时间同步操作
• next() 必须调用 :否则导航会挂起
•
组件守卫与组合式 API
:Vue 3 中可通过
onBeforeRouteUpdate
等组合式函数使用
通过合理使用 404 处理和路由钩子,可以实现:
• 更友好的错误提示
• 精细化的权限控制
• 导航过程的全链路监控
• 用户体验优化(如未保存提示)