Compare commits

...

7 Commits

23 changed files with 1440 additions and 5 deletions

View File

@ -4,13 +4,16 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"dev": "pnpm serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"animate.css": "^4.1.1",
"core-js": "^3.8.3",
"nanoid": "^5.1.5",
"vue": "^2.6.14",
"vue-router": "3",
"vue-router": "^3.6.5",
"vuex": "3"
},
"devDependencies": {

View File

@ -8,14 +8,20 @@ importers:
.:
dependencies:
animate.css:
specifier: ^4.1.1
version: 4.1.1
core-js:
specifier: ^3.8.3
version: 3.43.0
nanoid:
specifier: ^5.1.5
version: 5.1.5
vue:
specifier: ^2.6.14
version: 2.7.16
vue-router:
specifier: '3'
specifier: ^3.6.5
version: 3.6.5(vue@2.7.16)
vuex:
specifier: '3'
@ -1031,6 +1037,9 @@ packages:
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
animate.css@4.1.1:
resolution: {integrity: sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@ -2577,6 +2586,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@5.1.5:
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
engines: {node: ^18 || >=20}
hasBin: true
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@ -5237,6 +5251,8 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
animate.css@4.1.1: {}
ansi-colors@4.1.3: {}
ansi-escapes@3.2.0: {}
@ -6662,6 +6678,8 @@ snapshots:
nanoid@3.3.11: {}
nanoid@5.1.5: {}
natural-compare@1.4.0: {}
negotiator@0.6.3: {}

View File

@ -6,7 +6,12 @@
<hr />
<Demo3 /> -->
<!-- <Demo4 /> -->
<Demo6 />
<!-- <Demo6 /> -->
<!-- <Todo /> -->
<!-- <Demo8 /> -->
<!-- <Demo9 /> -->
<!-- <Demo10 /> -->
<Demo11 />
</div>
</template>
@ -16,11 +21,15 @@
// import Demo3 from "@/views/demo3/index.vue";
// import Demo4 from "@/views/demo4/index.vue";
// import Demo5 from "@/views/demo5/index.vue";
import Demo6 from "@/views/demo6/index.vue";
// import Todo from "@/views/todo/index.vue";
// import Demo8 from "@/views/demo8/index.vue";
// import Demo9 from "@/views/demo9/index.vue";
// import Demo10 from "@/views/demo10/index.vue";
import Demo11 from "@/views/demo11/index.vue";
export default {
name: "App",
// components: { Demo1, Demo2, Demo3 },
components: { Demo6 },
components: { Demo11 },
};
</script>

View File

@ -1,3 +1,4 @@
import "animate.css";
import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";

View File

@ -3,10 +3,12 @@ import VueRouter from "vue-router";
const router = new VueRouter({
routes: [
{
name: "About",
path: "/about",
component: () => import("@/views/demo6/components/About.vue"),
},
{
name: "Home",
path: "/home",
component: () => import("@/views/demo6/components/Home.vue"),
},

View File

@ -0,0 +1,175 @@
# Vue2 Transition 动画系统详解
## 一、基本动画实现
### 1. Transition 组件核心用法
```html
<Transition name="bunny" :appear="true">
<div v-show="visibility">内容</div>
</Transition>
```
| 属性 | 说明 | 示例值 |
| -------- | -------------------- | -------- |
| `name` | 动画类名前缀 | "bunny" |
| `appear` | 初始渲染是否应用动画 | true |
| `mode` | 过渡模式 | "out-in" |
### 2. 动画类名系统
```css
/* 进入动画 */
.bunny-enter-active { /* 激活阶段样式 */ }
.bunny-enter { /* 开始状态 */ }
.bunny-enter-to { /* 结束状态 */ }
/* 离开动画 */
.bunny-leave-active { /* 激活阶段样式 */ }
.bunny-leave { /* 开始状态 */ }
.bunny-leave-to { /* 结束状态 */ }
```
## 二、动画效果进阶
### 1. CSS 动画实现
```css
/* 关键帧动画 */
@keyframes slip {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.bunny-enter-active {
animation: slip 0.5s ease-out;
}
.bunny-leave-active {
animation: slip 0.5s ease-in reverse;
}
```
### 2. 过渡效果实现
```css
/* 过渡效果 */
.bunny-enter-active, .bunny-leave-active {
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.bunny-enter, .bunny-leave-to {
opacity: 0;
transform: scale(0.8);
}
```
## 三、最佳实践指南
### 1. 性能优化建议
- 优先使用 `transform``opacity` 属性
- 避免动画期间触发布局重排
- 对移动设备减少动画持续时间
### 2. 动画调试技巧
```css
/* 调试用边框 */
.bunny-enter-active {
outline: 2px solid red;
}
```
### 3. 响应式动画方案
```javascript
computed: {
animationDuration() {
return this.isMobile ? 300 : 500
}
}
```
## 四、高级应用场景
### 1. 列表过渡
```html
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
</TransitionGroup>
<style>
.list-move {
transition: transform 0.5s ease;
}
</style>
```
### 2. 动态过渡
```html
<Transition :name="transitionName">
<!-- ... -->
</Transition>
<script>
data() {
return {
transitionName: isMobile ? 'slide' : 'fade'
}
}
</script>
```
### 3. JavaScript 钩子
```html
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<!-- ... -->
</Transition>
<script>
methods: {
beforeEnter(el) {
el.style.opacity = 0
},
enter(el, done) {
anime({
targets: el,
opacity: 1,
duration: 500,
complete: done
})
}
}
</script>
```
## 五、常见问题解决方案
1. **动画不生效排查**
- 检查类名前缀是否匹配
- 确认元素初始状态是否正确
- 验证CSS属性是否可动画
2. **闪烁问题处理**
```css
.v-enter {
opacity: 0.01; /* 避免初始状态检测问题 */
}
```
3. **多元素过渡**
```html
<Transition mode="out-in">
<div :key="currentView">{{ currentView }}</div>
</Transition>
```
## 六、与Vue3的对比
| 特性 | Vue2 | Vue3 |
| -------- | ---------------------- | ---------------- |
| 类名前缀 | `v-` 或自定义 | 相同 |
| 过渡模式 | 支持 `in-out`/`out-in` | 新增 `in-out-in` |
| 性能 | 基于CSS | 基于FLIP优化 |
| 新特性 | - | 过渡持久化 |

View File

@ -0,0 +1,166 @@
# Vue2 过渡动画与 Animate.css 整合指南
## 一、Transition 与 TransitionGroup 对比
| 特性 | Transition | TransitionGroup |
| -------- | ----------------- | --------------- |
| 用途 | 单个元素/组件过渡 | 列表项过渡 |
| 要求 | 需要条件渲染 | 需要 `key` 属性 |
| 动画类 | 自动生成 | 可自定义类名 |
| 额外效果 | - | 自动处理定位 |
## 二、Animate.css 集成方案
### 1. 基本集成方法
```html
<TransitionGroup
name="animate__animated animate__bounce"
appear
enter-active-class="animate__flipInX"
leave-active-class="animate__flipOutX"
>
<!-- 列表项 -->
</TransitionGroup>
```
### 2. 常用动画效果
| 进入效果 | 离开效果 | 说明 |
| ---------------------- | ------------------------ | -------- |
| `animate__fadeIn` | `animate__fadeOut` | 淡入淡出 |
| `animate__zoomIn` | `animate__zoomOut` | 缩放效果 |
| `animate__slideInLeft` | `animate__slideOutRight` | 滑动效果 |
| `animate__flipInX` | `animate__flipOutX` | 3D翻转 |
## 三、自定义动画与库动画结合
### 1. 混合使用示例
```html
<!-- 自定义单个元素动画 -->
<Transition name="bunny">
<h3 v-show="visibility">自定义动画</h3>
</Transition>
<!-- Animate.css 列表动画 -->
<TransitionGroup
enter-active-class="animate__animated animate__fadeInUp"
leave-active-class="animate__animated animate__fadeOutDown"
>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</TransitionGroup>
```
### 2. 动画性能优化
```css
/* 启用GPU加速 */
.animate__animated {
animation-duration: 0.5s;
backface-visibility: hidden;
}
/* 限制动画影响范围 */
.bunny-enter-active, .bunny-leave-active {
will-change: transform;
isolation: isolate;
}
```
## 四、最佳实践建议
### 1. 动画选择原则
- **移动端**使用较轻量的动画如fade
- **重要交互**使用明显的反馈动画如bounce
- **连续操作**:保持动画风格一致
### 2. 时间控制技巧
```javascript
// 动态控制动画时长
data() {
return {
duration: window.innerWidth < 768 ? 300 : 500
}
}
```
### 3. 无障碍访问
```css
/* 减少运动偏好设置 */
@media (prefers-reduced-motion: reduce) {
.animate__animated,
.bunny-enter-active,
.bunny-leave-active {
animation: none !important;
transition: none !important;
}
}
```
## 五、进阶应用场景
### 1. 路由过渡动画
```html
<Transition :name="transitionName" mode="out-in">
<router-view></router-view>
</Transition>
<script>
export default {
data() {
return {
transitionName: 'slide'
}
},
watch: {
'$route'(to, from) {
this.transitionName = to.meta.transition || 'fade'
}
}
}
</script>
```
### 2. 状态驱动的动画
```javascript
computed: {
animationClass() {
return this.isError
? 'animate__shakeX'
: 'animate__tada'
}
}
```
### 3. 动画事件钩子
```html
<Transition
@before-enter="beforeEnter"
@after-enter="afterEnter"
>
<div v-if="show">内容</div>
</Transition>
<script>
methods: {
beforeEnter(el) {
el.style.opacity = 0
},
afterEnter(el) {
// 动画结束后的处理
}
}
</script>
```
## 六、常见问题解决
1. **动画不连贯**
- 使用 `mode="out-in"` 确保顺序执行
- 检查 `key` 属性是否唯一
2. **初始渲染无动画**
- 添加 `appear` 属性
- 检查初始状态是否正确
3. **移动端性能差**
- 减少动画复杂度
- 使用 `will-change` 提示浏览器优化

View File

@ -0,0 +1,64 @@
<template>
<div class="container card">
<div class="card-header">
<h1>动画效果:{{ visibility }}</h1>
</div>
<div class="card-body">
<Transition name="bunny" :appear="true">
<h3 v-show="visibility">显示动画</h3>
</Transition>
<ul>
<TransitionGroup
name="animate__animated animate__bounce"
appear
enter-active-class="animate__flipInX"
leave-active-class="animate__flipOutX"
>
<li key="1" v-show="visibility">嗬嗬嗬嗬嗬嗬嗬嗬嗬嗬嗬</li>
<li key="2" v-show="!visibility">哈哈哈哈哈哈哈哈哈哈哈</li>
</TransitionGroup>
</ul>
</div>
<div class="card-footer">
<div class="btn-group">
<button class="btn btn-primary" @click="visibility = true">显示</button>
<button class="btn btn-success" @click="visibility = false">
隐藏
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Demo-10",
data() {
return {
visibility: true,
};
},
};
</script>
<style>
.bunny-enter-active {
animation: slip 0.5s linear;
}
.bunny-leave-active {
animation: slip 0.5s linear reverse;
}
@keyframes slip {
from {
transform: translateX(-300px);
}
to {
transform: translateX(0);
}
}
</style>

View File

@ -0,0 +1,175 @@
# Vue2 路由缓存与导航控制最佳实践
## 一、Keep-Alive 路由缓存机制
### 1. 基本使用方式
```html
<!-- 缓存单个组件 -->
<keep-alive include="Home">
<router-view/>
</keep-alive>
<!-- 缓存多个组件 -->
<keep-alive :include="['Home', 'About']">
<router-view/>
</keep-alive>
```
### 2. 核心属性说明
| 属性 | 类型 | 说明 |
| --------- | ------------------- | ---------------------- |
| `include` | String/Regexp/Array | 匹配的组件名将被缓存 |
| `exclude` | String/Regexp/Array | 匹配的组件名不会被缓存 |
| `max` | Number | 最大缓存组件数 |
## 二、组件生命周期变化
### 1. 缓存状态下的生命周期
```
首次加载created → mounted → activated
离开组件deactivated
再次进入activated
```
### 2. 新增生命周期钩子
```javascript
export default {
activated() {
// 组件被激活时调用
this.fetchData()
},
deactivated() {
// 组件被停用时调用
this.clearTimer()
}
}
```
## 三、最佳实践建议
### 1. 缓存策略优化
```html
<!-- 动态决定是否缓存 -->
<keep-alive :include="cachedViews">
<router-view :key="$route.fullPath"/>
</keep-alive>
<script>
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
}
}
</script>
```
### 2. 数据刷新方案
```javascript
// 方案1监听路由变化
watch: {
'$route'(to, from) {
if (to.name === 'Home') {
this.refreshData()
}
}
}
// 方案2使用activated钩子
activated() {
this.loadData()
}
```
### 3. 内存管理技巧
```javascript
// 清除缓存数据
deactivated() {
this.list = []
this.pagination = { page: 1, size: 10 }
}
```
## 四、高级应用场景
### 1. 多级路由缓存
```javascript
// 父路由配置
{
path: '/nested',
component: Layout,
children: [
{
path: 'child1',
component: Child1,
meta: { keepAlive: true }
},
{
path: 'child2',
component: Child2
}
]
}
// 动态缓存
<keep-alive>
<router-view v-if="$route.meta.keepAlive"/>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"/>
```
### 2. 滚动位置保持
```javascript
// router配置
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else if (to.meta.keepAlive) {
return { x: 0, y: to.meta.scrollTop || 0 }
}
return { x: 0, y: 0 }
}
// 组件内记录位置
deactivated() {
this.$route.meta.scrollTop = document.documentElement.scrollTop
}
```
### 3. 缓存状态管理
```javascript
// 动态移除缓存
this.$vnode.parent.componentInstance.cache = {}
this.$vnode.parent.componentInstance.keys = []
```
## 五、常见问题解决方案
1. **缓存不生效排查**
- 检查组件`name`是否与`include`匹配
- 验证路由配置是否正确
- 确保没有使用`v-if`干扰
2. **数据不更新问题**
```javascript
// 使用路由参数作为key强制更新
<router-view :key="$route.fullPath"/>
```
3. **内存泄漏预防**
```javascript
deactivated() {
// 清除定时器/事件监听
clearInterval(this.timer)
window.removeEventListener('resize', this.handleResize)
}
```
## 六、与Vue3的区别
| 特性 | Vue2 | Vue3 |
| --------- | ----------------------- | ------------------------- |
| 缓存控制 | `include/exclude` | 新增`max`属性 |
| 生命周期 | `activated/deactivated` | 需要导入`onActivated`钩子 |
| 路由缓存 | 需要`keep-alive` | 内置路由组件缓存 |
| 组合式API | 不支持 | 提供`onActivated`等钩子 |

View File

@ -0,0 +1,50 @@
<template>
<div class="container card">
<div class="card-header btn-group">
<!-- 路由导航 -->
<button class="btn btn-success" @click="$router.push({ name: 'About' })">
显示About
</button>
<button class="btn btn-success" @click="$router.push({ name: 'Home' })">
显示Home
</button>
<!-- 前进和后退 -->
<button class="btn btn-primary" @click="back">后退</button>
<button class="btn btn-dark" @click="forward">前进</button>
</div>
<div class="card-body">
<!-- include 包含的是组件名让不展示的路由保持挂载不被销毁 -->
<!-- <keep-alive include="Home"> -->
<keep-alive :include="['Home', 'About']">
<router-view />
</keep-alive>
</div>
</div>
</template>
<script>
export default {
name: "Demo-11",
data() {
return {};
},
methods: {
/* 后退 */
back() {
this.$router.back();
},
/* 前进 */
forward() {
this.$router.forward();
},
/* 跳转指定步数 */
go() {
this.$router.go(-2);
},
},
};
</script>

View File

@ -7,5 +7,11 @@
<script>
export default {
name: "AboutPage",
beforeDestroy() {
console.log("AboutPage销毁");
},
mounted() {
console.log("AboutPage挂载");
},
};
</script>

View File

@ -7,5 +7,11 @@
<script>
export default {
name: "HomePage",
beforeDestroy() {
console.log("HomePage销毁");
},
mounted() {
console.log("HomePage挂载");
},
};
</script>

View File

@ -0,0 +1,44 @@
<template>
<div
class="card-footer d-flex justify-content-between align-items-center"
v-if="count"
>
<div @click="onSelectAll(selectAll)">
<input type="checkbox" class="me-2" :checked="selectAll" />
<span>已完成 {{ accomplish }} / 全部 {{ count }}</span>
</div>
<button class="btn btn-danger" @click="onDeletedCommpleted">
清除已完成
</button>
</div>
</template>
<script>
export default {
name: "TodoFooter",
props: ["list", "onSelectAll", "deletedTodo"],
computed: {
/* 完成的数量 */
accomplish() {
return this.list.filter((todo) => todo.completed).length;
},
/* 总数量 */
count() {
return this.list.length;
},
/* 是否已经全选 */
selectAll() {
const flag = this.list.filter((todo) => todo.completed === true);
return flag.length === this.list.length;
},
},
methods: {
onDeletedCommpleted() {
this.$emit("deleted-commpleted");
},
},
};
</script>

View File

@ -0,0 +1,43 @@
<template>
<div class="card-header my-3">
<input
type="text"
v-model.trim="content"
class="form-control"
placeholder="用户的输入..."
@keyup.enter="onAdd"
/>
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "TodoHeader",
data() {
return {
content: "",
};
},
methods: {
onAdd(ev) {
if (!this.content) {
alert("没有内容!!!");
return;
}
const todoObj = {
id: nanoid(),
content: ev.target.value.trim(),
completed: false,
};
this.$emit("onAdd", todoObj);
//
this.content = "";
},
},
};
</script>

View File

@ -0,0 +1,47 @@
<template>
<li
class="list-group-item d-flex justify-content-between align-items-center list-group-item-action"
>
<div class="py-2" @click="onChange">
<input type="checkbox" class="me-2" :checked="todo.completed" />
<span> {{ todo.content }}</span>
</div>
<button class="btn btn-danger deleted" @click="onDeleted">删除</button>
</li>
</template>
<script>
export default {
name: "TodoItem",
props: ["todo"],
methods: {
/* 修改todo的状态 */
onChange() {
const id = this.todo.id;
this.$root.$emit("change-todo-status", id);
},
/* 删除todo */
onDeleted() {
const id = this.todo.id;
const result = confirm(`确认删除:${id}`);
if (result) {
this.$root.$emit("delete-todo", id);
}
},
},
};
</script>
<style scoped>
.deleted {
visibility: hidden;
}
li:hover .deleted {
visibility: visible !important;
}
</style>

View File

@ -0,0 +1,15 @@
<template>
<ul class="list-group card-body">
<todo-item v-for="todo in list" :key="todo.id" :todo="todo" />
</ul>
</template>
<script>
import TodoItem from "./todo-item";
export default {
name: "TodoList",
components: { TodoItem },
props: ["list"],
};
</script>

View File

@ -0,0 +1,89 @@
<template>
<div class="container card">
<todo-header @onAdd="onAdd" />
<todo-list :list="todolist" />
<todo-footer
:list="todolist"
:on-select-all="onSelectAll"
@deleted-commpleted="onDeletedCommpleted"
/>
</div>
</template>
<script>
import TodoFooter from "./components/todo-footer";
import TodoHeader from "./components/todo-header";
import TodoList from "./components/todo-list";
export default {
name: "TODOList",
components: { TodoHeader, TodoList, TodoFooter },
data() {
return {
todolist: [
{ id: 1, content: "吃饭", completed: true },
{ id: 2, content: "吃饭", completed: false },
{ id: 3, content: "吃饭", completed: true },
],
};
},
methods: {
/* 添加Todo内容 */
onAdd(todo) {
this.todolist.unshift(todo);
},
/* 修改todo状态是否完成 */
changeTodoStatus() {
//
this.$root.$off("change-todo-status");
this.$root.$on("change-todo-status", (id) => {
this.todolist.forEach((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
});
});
},
/* 删除todo */
deletedTodo() {
this.$root.$off("delete-todo");
this.$root.$on("delete-todo", (id) => {
const list = this.todolist.filter((todo) => todo.id !== id);
this.todolist = list;
});
},
/* 删除已完成的todo */
onDeletedCommpleted() {
const ids = this.todolist
.filter((todo) => todo.completed === true)
.map((todo) => todo.id);
const completedList = this.todolist.filter(
(todo) => todo.completed === false
);
//
const result = confirm(`是否确认删除:${ids}`);
if (result) {
this.todolist = completedList;
alert(`清除已完成id${ids}`);
}
},
/* 选择全部 */
onSelectAll(selectAll) {
this.todolist = this.todolist.map((todo) => ({
...todo,
completed: !selectAll,
}));
},
},
mounted() {
this.changeTodoStatus();
this.deletedTodo();
},
};
</script>

View File

@ -0,0 +1,174 @@
# Vue2 组件通信与事件处理最佳实践
## 一、事件系统核心机制
### 1. 事件修饰符对比
| 修饰符 | 说明 | 示例 |
| ---------- | ------------ | --------------------------- |
| `.once` | 只触发一次 | `@click.once="handler"` |
| `.native` | 监听原生事件 | `@click.native="handler"` |
| `.stop` | 阻止事件冒泡 | `@click.stop="handler"` |
| `.prevent` | 阻止默认行为 | `@submit.prevent="handler"` |
### 2. 事件绑定与解绑
```javascript
// 子组件 StudentInfo.vue
methods: {
emitEvent() {
this.$emit('custom-event', payload)
},
unbind() {
this.$off('custom-event') // 解绑特定事件
this.$off() // 解绑所有事件
}
}
```
## 二、组件通信模式详解
### 1. Props 向下传递
```javascript
// 父组件
<Child :title="pageTitle" />
// 子组件
props: {
title: {
type: String,
default: '默认标题'
}
}
```
### 2. Events 向上通信
```javascript
// 子组件
this.$emit('update', newValue)
// 父组件
<Child @update="handleUpdate" />
```
### 3. 高级通信方式
| 方式 | 适用场景 | 示例 |
| -------------- | ---------- | ------------------------------------- |
| ref | 父调子方法 | `this.$refs.child.method()` |
| provide/inject | 跨级组件 | `provide() { return { key: value } }` |
| Event Bus | 任意组件 | `Vue.prototype.$bus = new Vue()` |
| Vuex | 全局状态 | `this.$store.commit('mutation')` |
## 三、最佳实践指南
### 1. 事件命名规范
- 使用 kebab-case 命名(如 `user-updated`
- 避免与原生事件重名(如 `click`
- 语义化命名(如 `form-submitted`
### 2. 性能优化建议
```javascript
// 1. 适时解绑事件
beforeDestroy() {
this.$off('custom-event')
}
// 2. 防抖处理高频事件
methods: {
handleInput: _.debounce(function() {
// 处理逻辑
}, 500)
}
```
### 3. 安全通信模式
```javascript
// 添加事件存在性检查
if (this._events['custom-event']) {
this.$emit('custom-event', data)
}
```
## 四、高级应用场景
### 1. 动态事件处理
```javascript
// 根据条件绑定不同处理函数
<template>
<Child @event="handlerMap[handlerType]" />
</template>
<script>
export default {
data() {
return {
handlerType: 'typeA',
handlerMap: {
typeA: this.handleTypeA,
typeB: this.handleTypeB
}
}
}
}
</script>
```
### 2. 自定义事件总线
```javascript
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A
EventBus.$emit('message', 'Hello')
// 组件B
EventBus.$on('message', (msg) => {
console.log(msg)
})
```
### 3. 异步事件处理
```javascript
// 返回Promise的事件处理
methods: {
async submitForm() {
try {
await this.$emitAsync('form-submit')
this.showSuccess()
} catch (error) {
this.showError(error)
}
}
}
```
## 五、常见问题解决方案
1. **事件未触发排查**
- 检查事件名称大小写是否一致
- 确认子组件是否正确调用 `$emit`
- 验证父组件监听的事件名是否匹配
2. **内存泄漏预防**
```javascript
// 清除所有监听器
beforeDestroy() {
this.$off()
EventBus.$off('event', this.handler)
}
```
3. **跨组件通信优化**
```javascript
// 使用Vue.observable实现轻量状态管理
const state = Vue.observable({ count: 0 })
```
## 六、与Vue3的对比
| 特性 | Vue2 | Vue3 |
| -------- | ----------- | ---------------- |
| 事件系统 | `$emit/$on` | `emits` 选项声明 |
| 移除API | - | `$off`, `$once` |
| 新特性 | - | `v-model` 参数 |
| 性能 | 基于观察者 | 基于Proxy |

View File

@ -0,0 +1,59 @@
<template>
<div class="card">
<div class="card-header">
<div class="card-header-tabs">
<h3>{{ title }}</h3>
</div>
<div class="card-header-pills">
<h3>名称{{ userinfo.sutdentName }}</h3>
<h3>地址{{ userinfo.address }}</h3>
</div>
</div>
<div class="card-body btn-group">
<button class="btn btn-primary" @click="getUserinfo">获取用户信息</button>
<button class="btn btn-danger" @click="unbind">解除绑定</button>
</div>
</div>
</template>
<script>
export default {
name: "StudentInfo",
props: {
title: {
type: String,
default: "标题显示",
},
},
data() {
return {
userinfo: {
sutdentName: "Bunny",
address: "昆山印象花园",
},
};
},
methods: {
/* 触发用户事件 */
getUserinfo() {
this.$emit("getUserinfo", this.userinfo);
},
/* 接触绑定 */
unbind() {
console.info("解除绑定");
//
this.$off("getUserinfo");
//
// this.$off(["getUserinfo"]);
//
// this.$off();
},
},
};
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="container">
<!-- 可以只触发一次 -->
<Student @getUserinfo.once="getUserinfo" title="可以只触发一次" />
<Student @getUserinfo="getUserinfo" title="普通触发" />
</div>
</template>
<script>
import Student from "./components/Sutdent";
export default {
name: "Demo-8",
components: { Student },
data() {
return {};
},
methods: {
/* 获取用户信息 */
getUserinfo(userinfo) {
const data = { ...userinfo };
console.log(data);
},
},
};
</script>

View File

@ -0,0 +1,186 @@
# Vue2 组件通信与事件处理深度指南
## 一、组件通信核心方式
### 1. 父子组件通信模式
#### Props 向下传递
```javascript
// 父组件
<Child :title="pageTitle" />
// 子组件
props: {
title: String
}
```
#### Events 向上传递
```javascript
// 子组件
this.$emit('update', newValue)
// 父组件
<Child @update="handleUpdate" />
```
### 2. 引用直接访问
```javascript
// 父组件
<Child ref="childRef" />
methods: {
callChildMethod() {
this.$refs.childRef.childMethod()
}
}
```
## 二、事件系统高级用法
### 1. 原生事件绑定
```html
<!-- 必须添加.native修饰符 -->
<StudentInfo @click.native="handleClick" />
```
### 2. 手动事件管理
```javascript
// 绑定事件推荐在mounted钩子中
mounted() {
this.$refs.studenInfo.$on("getStudentName", this.getStudentName)
}
// 解绑事件必须在beforeDestroy中清理
beforeDestroy() {
this.$refs.studenInfo.$off("getStudentName")
}
```
### 3. 事件参数传递
```javascript
// 子组件触发时可带多个参数
this.$emit('event-name', arg1, arg2, ...)
// 父组件接收所有参数
handler(arg1, arg2, ...rest) {
// 处理参数
}
```
## 三、最佳实践建议
### 1. 事件命名规范
- 使用 kebab-case 命名(如 `student-updated`
- 避免与原生事件重名(添加业务前缀)
- 语义化命名(如 `form-submitted`
### 2. 安全通信模式
```javascript
// 添加事件存在性检查
if (this._events['custom-event']) {
this.$emit('custom-event', data)
}
// 使用try-catch包裹可能出错的事件处理
try {
this.$emit('critical-action', payload)
} catch (error) {
console.error('事件处理失败:', error)
}
```
### 3. 性能优化方案
```javascript
// 防抖处理高频事件
methods: {
handleInput: _.debounce(function() {
this.$emit('input-changed', this.value)
}, 300)
}
```
## 四、高级通信场景
### 1. 跨级组件通信
```javascript
// 祖先组件
provide() {
return {
appData: this.sharedData
}
}
// 后代组件
inject: ['appData']
```
### 2. 动态事件处理器
```javascript
// 根据状态切换处理函数
<Child @custom-event="handlerMap[currentHandler]" />
data() {
return {
currentHandler: 'default',
handlerMap: {
default: this.handleDefault,
special: this.handleSpecial
}
}
}
```
### 3. 事件总线模式
```javascript
// event-bus.js
import Vue from 'vue'
export default new Vue()
// 组件A
EventBus.$emit('data-updated', payload)
// 组件B
EventBus.$on('data-updated', handler)
```
## 五、常见问题解决方案
1. **事件未触发排查**
- 检查组件引用是否正确(`ref` 命名)
- 确认事件名称完全匹配(大小写敏感)
- 验证事件绑定时机(确保在 mounted 之后)
2. **内存泄漏预防**
```javascript
beforeDestroy() {
// 清除所有自定义事件监听
this.$off()
// 清除事件总线监听
EventBus.$off('data-updated', this.handler)
}
```
3. **异步事件处理**
```javascript
// 返回Promise的事件处理
async handleEvent() {
try {
await this.$nextTick()
const result = await this.$emitAsync('async-event')
// 处理结果
} catch (error) {
// 错误处理
}
}
```
## 六、与Vue3的对比
| 特性 | Vue2 | Vue3 |
| -------- | -------------- | -------------------- |
| 事件定义 | 隐式声明 | `emits` 选项显式声明 |
| 原生事件 | 需要 `.native` | 自动识别 |
| 移除API | - | 移除 `$on`, `$off` |
| 性能 | 基于观察者 | 基于Proxy的响应式 |

View File

@ -0,0 +1,31 @@
<template>
<div class="card">
<div class="card-header">
<h3>学生姓名{{ studenName }}</h3>
</div>
<div class="card-body">
<div class="btn-group">
<button class="btn btn-primary" @click="onEmitStudentName">
向父组件传递学生姓名
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "StudentInfo",
data() {
return {
studenName: "Bunny",
};
},
methods: {
onEmitStudentName() {
this.$emit("getStudentName", this.studenName);
},
},
};
</script>

View File

@ -0,0 +1,47 @@
<template>
<div class="container">
<h4>
学生姓名
<span class="badge text-bg-secondary"> {{ studenName }}</span>
</h4>
<!-- 需要在前面加上native否则会当成自定义事件 -->
<StudentInfo ref="studenInfo" @click.native="showMessage" />
</div>
</template>
<script>
import StudentInfo from "./components/SutdentInfo.vue";
export default {
name: "Demo-9",
components: { StudentInfo },
data() {
return {
studenName: "",
};
},
methods: {
/* 获取学生名 */
getStudentName(name, ...params) {
console.log(name, params);
this.studenName = name;
},
/* 点击子组件显示消息 */
showMessage() {
alert("点击子组件触发点击事件。。。");
},
},
mounted() {
this.$refs.studenInfo.$on("getStudentName", this.getStudentName);
// thisVue
// this.$refs.studenInfo.$on("getStudentName", (name, ...params) => {
// console.log(name, params);
// this.studenName = name;
// });
},
};
</script>