生命周期和数据共享
# 生命周期和数据共享
# 1. 组件的生命周期
组件的生命周期指的是组件从创建、更新到销毁的整个过程。在这个过程中,Vue 提供了一系列的生命周期钩子函数,让开发者可以在组件的不同阶段执行特定的操作。
# 1.1 生命周期 & 生命周期函数
- 生命周期(Life Cycle):指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
- 生命周期函数(Lifecycle Hook):由 Vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行,强调的是时间点。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点。
# 1.2 组件生命周期函数的分类
组件的生命周期函数主要分为三个阶段:
- 创建阶段:组件实例被创建。
- 运行阶段:组件的响应式数据发生变化时调用。
- 销毁阶段:组件实例被销毁。
# 1.3 生命周期图示
# 1.4 组件生命周期函数表格
生命周期函数 | 执行时机 | 所属阶段 | 执行次数 | 应用场景 |
---|---|---|---|---|
beforeCreate | 组件实例被创建之前调用 | 创建阶段 | 一次 | 在数据观测和事件配置之前进行初始化操作 |
created | 组件实例被创建之后调用 | 创建阶段 | 一次 | 完成数据观测、属性和方法的初始化,未挂载DOM |
beforeMount | 组件的 DOM 元素被插入到页面之前调用 | 创建阶段 | 一次 | 在初次渲染之前执行操作,可以访问DOM节点 |
mounted | 组件的 DOM 元素被插入到页面之后调用 | 创建阶段 | 一次 | 完成初次渲染,进行依赖于DOM的操作 |
beforeUpdate | 组件数据更新之前调用 | 运行阶段 | 多次 | 在数据更新后、DOM重新渲染前执行操作 |
updated | 组件数据更新之后调用 | 运行阶段 | 多次 | 数据更新并重新渲染DOM后执行操作 |
beforeDestroy | 组件实例被销毁之前调用 | 销毁阶段 | 一次 | 在组件销毁前执行清理操作 |
destroyed | 组件实例被销毁之后调用 | 销毁阶段 | 一次 | 完成销毁,清理相关资源 |
activated | 组件被激活时调用 | 活跃阶段 | 多次 | 在 keep-alive 组件激活时调用 |
deactivated | 组件被停用时调用 | 活跃阶段 | 多次 | 在 keep-alive 组件停用时调用 |
errorCaptured | 捕获错误的钩子,发生错误时调用 | 错误阶段 | 多次 | 捕获子组件渲染过程中的错误,进行错误处理 |
通过这些生命周期函数,开发者可以在组件的不同生命周期阶段执行自定义的逻辑,从而更好地控制组件的行为和状态。
示例代码
export default {
// 组件实例被创建之前调用
beforeCreate() {
console.log('beforeCreate: 组件实例被创建之前调用');
},
// 组件实例被创建之后调用
created() {
console.log('created: 组件实例被创建之后调用');
// 在 created 钩子中发送 AJAX 请求获取初始数据
axios.get('/api/data')
.then(response => {
this.data = response.data;
})
.catch(error => {
console.error('Error fetching data:', error);
});
},
// 组件的 DOM 元素被插入到页面之前调用
beforeMount() {
console.log('beforeMount: 组件的 DOM 元素被插入到页面之前调用');
},
// 组件的 DOM 元素被插入到页面之后调用
mounted() {
console.log('mounted: 组件的 DOM 元素被插入到页面之后调用');
// 在 mounted 钩子中操作 DOM 元素
this.$refs.myElement.focus();
},
// 组件数据更新之前调用
beforeUpdate() {
console.log('beforeUpdate: 组件数据更新之前调用');
},
// 组件数据更新之后调用
updated() {
console.log('updated: 组件数据更新之后调用');
},
// 组件实例被销毁之前调用
beforeDestroy() {
console.log('beforeDestroy: 组件实例被销毁之前调用');
},
// 组件实例被销毁之后调用
destroyed() {
console.log('destroyed: 组件实例被销毁之后调用');
},
// 组件被激活时调用
activated() {
console.log('activated: 组件被激活时调用');
},
// 组件被停用时调用
deactivated() {
console.log('deactivated: 组件被停用时调用');
},
// 捕获错误的钩子
errorCaptured(err, vm, info) {
console.log('errorCaptured: 捕获错误的钩子', err, vm, info);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
在以上示例中:
created
钩子在组件实例被创建之后调用,此时数据观测、属性和方法已经初始化完成,可以在这里发送 AJAX 请求获取初始数据。mounted
钩子在组件的 DOM 元素被插入到页面之后调用,可以在这里进行依赖于 DOM 的操作,例如获取焦点、操作 DOM 元素等。
为什么不在 `beforeCreate` 中发送 AJAX 请求获取初始数据?
在 beforeCreate
钩子中,组件实例刚刚被创建,此时数据观测和事件配置尚未完成,组件实例的属性和方法还无法使用。因此,发送 AJAX 请求获取初始数据的最佳时机是在 created
钩子中。此时,组件实例的属性和方法已经初始化完成,可以安全地进行数据操作和异步请求。
# 1.5 组件销毁与数据处理
在 Vue.js 中,当通过路由切换组件时,原组件实例会被销毁,这导致:
组件数据和状态清除:
- 组件中的所有
data
和computed
属性,以及本地状态(如局部变量),都会被清除。 - 组件的 DOM 元素也会被移除。
- 组件中的所有
事件监听器注销:
- 如果组件在使用全局事件总线(EventBus)时注册了事件监听器,这些监听器会被销毁。
- 为防止内存泄漏,应在组件的
beforeDestroy
或destroyed
生命周期钩子中手动注销这些事件监听器。
如果要进行数据的持久化存储,可使用 Vuex、
localStorage
、sessionStorage
等持久化存储机制。
使用 Vuex 持久化数据的好处
- 数据共享与持久化:通过 Vuex,可以在多个组件之间共享数据,并在组件切换时保持数据持久性。
- 状态管理:Vuex 提供了集中式状态管理,使得应用中的状态可预测且易于管理。
# 2. Vue 组件之间的数据共享
组件的关系:在 Vue 中,组件之间的关系可以分为父子组件和兄弟组件。不同关系的组件之间的数据共享方式也有所不同。
# 2.1 父子组件之间的数据共享
父子组件之间的数据共享通过自定义属性和自定义事件来实现。
# 1-1 父组件向子组件共享数据
父组件向子组件共享数据需要使用 props
。通过在子组件中定义 props
属性,父组件可以通过自定义属性向子组件传递数据。
提示
在 Vue 中,props
可以定义的类型包括:String
(字符串)、Number
(数字)、Boolean
(布尔值)、Array
(数组)、Object
(对象)、Function
(函数)和任意类型 Any
(可以是多种类型的组合)。
示例代码
# 1. 父组件
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 通过自定义属性 message 向子组件传递数据 -->
<ChildComponent :message="parentMessage"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent // 注册子组件
},
data() {
return {
parentMessage: 'Hello from Parent' // 父组件的数据
};
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2. 子组件
<!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p> <!-- 使用父组件传递的数据 -->
</div>
</template>
<script>
export default {
props: ['message'] // 定义 props 接收父组件传递的数据
};
</script>
2
3
4
5
6
7
8
9
10
11
12
在这个示例中,父组件通过自定义属性 message
向子组件传递数据,子组件通过 props
接收并使用这个数据。
# 1-2 子组件向父组件共享数据
子组件向父组件共享数据使用自定义事件。通过在子组件中使用this.$emit
触发自定义事件,父组件可以通过监听这些事件来接收子组件传递的数据。
子组件中使用 this.$emit
触发事件:
this.$emit('updateMessage', 'Hello from Child')
中的'updateMessage'
是事件名,父组件监听这个事件。'Hello from Child'
是传递给父组件的数据。
父组件中监听子组件的事件:
- 父组件通过
@updateMessage="handleUpdateMessage"
监听子组件的'updateMessage'
事件。 - 当子组件触发
'updateMessage'
事件时,父组件会调用handleUpdateMessage
方法。 handleUpdateMessage(newMessage)
方法中的newMessage
参数就是子组件传递的数据'Hello from Child'
。
示例代码
# 1. 父组件
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 监听子组件的自定义事件,接收子组件传递的数据 -->
<ChildComponent @updateMessage="handleUpdateMessage"></ChildComponent>
<p>Received Message: {{ receivedMessage }}</p>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent // 注册子组件
},
data() {
return {
receivedMessage: '' // 存储子组件传递的数据
};
},
methods: {
// 处理子组件传递的数据
handleUpdateMessage(newMessage) {
this.receivedMessage = newMessage; // 更新父组件的数据
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2. 子组件
<!-- ChildComponent.vue -->
<template>
<div>
<!-- 通过按钮触发自定义事件,向父组件传递数据 -->
<button @click="updateMessage">Update Message</button>
</div>
</template>
<script>
export default {
methods: {
// 触发自定义事件并传递数据
updateMessage() {
this.$emit('updateMessage', 'Hello from Child'); // 触发名为 'updateMessage' 的自定义事件,并传递数据 'Hello from Child'
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
说明:
this.$emit
方法用于在子组件中触发父组件监听的事件。第一个参数是事件名,这个事件名是自定义的,并且需要与父组件中监听的事件名一致。在这个示例中,事件名是'updateMessage'
。- 第二个参数是要传递的数据。在这个示例中,传递的数据是
'Hello from Child'
。 $emit
中的$
符号是 Vue 的约定,用于表示 Vue 的内置方法和属性。
父组件监听到子组件的事件触发后,会调用相应的回调方法。在这个示例中,父组件监听的是 updateMessage
事件,并在 handleUpdateMessage
方法中处理子组件传递的数据。handleUpdateMessage
方法中的参数 newMessage
是从子组件传递过来的数据。
# 2.2 父子数据共享 (v-mode和.sync)
在 Vue.js 中,v-model
和 .sync
修饰符都用于实现父子组件之间的双向数据绑定,但它们的使用场景和具体实现略有不同。以下是它们的详细对比和应用场景:
# 1. v-model:简化表单控件的双向数据绑定
v-model
是 Vue.js 中用于简化表单控件双向数据绑定的语法糖。它将用户输入的值与数据模型同步,使得开发者不必手动编写数据绑定逻辑。
默认绑定属性和事件:
v-model
默认绑定value
作为prop
,并监听input
事件。这意味着,使用v-model
的表单控件会自动将用户的输入值更新到数据模型中。
自定义绑定属性和事件:
- 在自定义组件中,可以通过
model
选项指定自定义的prop
和事件,使得v-model
适用于更多场景。例如,可以将v-model
绑定到checked
属性并监听change
事件。
- 在自定义组件中,可以通过
使用示例:
<custom-input v-model="text"></custom-input>
1在
custom-input
组件中,Vue 默认将v-model
绑定到子组件props中的value
属性,子组件可以通过this.$emit('input', newValue)
事件将数据同步回父组件,父组件自动完成数据的更新,不需要手动编写事件去同步更新
# 2. .sync:通用的双向绑定修饰符
.sync
修饰符提供了一种更通用的方式来处理双向数据绑定,它适用于不只是表单控件的场景。.sync
允许开发者将任意 prop
绑定到父组件的属性上,并通过 update:propName
事件进行数据同步。
灵活性:
.sync
可以绑定任意prop
,不局限于value
。它为需要双向数据绑定的复杂场景提供了更高的灵活性。
事件机制:
- 在使用
.sync
时,子组件需要通过this.$emit('update:propName', newValue)
事件将更新后的数据发送回父组件。
- 在使用
使用示例:
<pagination :current-page.sync="page"></pagination>
1在
pagination
组件中,.sync
修饰符绑定子组件props中的currentPage
属性,子组件可以通过this.$emit('update:currentPage', newPage)
事件将数据同步到父组件,同样的父组件自动完成数据的更新,不需要手动编写事件去同步更新。
# 3. v-model 和 .sync 的对比
使用场景:
v-model
主要用于处理表单控件的双向数据绑定,如输入框、复选框等。.sync
更加通用,适用于任意需要双向绑定的场景,如分页组件中的current-page
属性。
默认行为:
v-model
默认绑定value
并监听input
事件,但可以通过model
选项自定义。.sync
没有默认的prop
或事件,开发者可以自由指定要绑定的prop
和触发的数据同步事件。
代码实现:
v-model
的底层实现与.sync
类似,都是通过prop
传递数据,并通过事件机制同步数据。但.sync
更加灵活,可以用于更广泛的场景。
总结
- 表单控件:使用
v-model
可以大大简化表单控件的数据绑定逻辑,适用于输入框、单选框、多选框等常见控件。 - 复杂组件:在需要将多个
prop
双向绑定时,.sync
提供了更大的灵活性,适用于分页组件、树形控件等复杂 UI 组件。
# 2.2 任意组件间的数据共享 (通信)
# 1-1 全局事件总线(GlobalEventBus)
全局事件总线是一种用于 Vue 组件之间通信的机制,适用于任意组件间通信。通过全局事件总线,组件可以发布和监听事件,实现数据和事件的共享。
创建 EventBus
在项目的入口文件(如
main.js
)中,在创建 Vue 实例的时候将其挂载到 Vue 的原型上作为事件总线。new Vue({ // main.js 创建 vm 时 ...... beforeCreate() { Vue.prototype.$bus = this; // 安装全局事件总线,this 就是当前应用的 vm }, ...... });
1
2
3
4
5
6
7
8Vue.prototype.$bus
被赋值为当前 Vue 实例(this
),这意味着应用程序的根实例本身充当事件总线。在组件中使用 EventBus
- 触发事件(发布事件):组件可以通过
this.$bus.$emit
来发布事件,并可传递数据。 - 监听事件(接收事件):组件可以通过
this.$bus.$on
来监听事件,并在事件触发时接收数据。
- 触发事件(发布事件):组件可以通过
解绑事件
为了避免内存泄漏,组件在销毁之前应该使用
this.$bus.$off
解绑它所监听的事件。
使用示例
# 1. 组件A触发事件
<!-- ComponentA.vue -->
<template>
<div>
<!-- 点击按钮触发事件,发送数据到其他组件 -->
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
export default {
methods: {
sendMessage() {
// 触发名为 'customEvent' 的事件,并传递数据 'Hello from A'
this.$bus.$emit('customEvent', 'Hello from A');
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2. 组件B监听事件
<!-- ComponentB.vue -->
<template>
<div>
<p>Message from A: {{ message }}</p> <!-- 显示接收到的数据 -->
</div>
</template>
<script>
export default {
data() {
return {
message: '' // 存储接收到的数据
};
},
mounted() {
// 监听事件 'customEvent',并在事件触发时更新数据
this.$bus.$on('customEvent', newMessage => {
this.message = newMessage;
});
},
beforeDestroy() {
// 组件销毁前取消事件监听,防止内存泄漏
this.$bus.$off('customEvent');
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
总结
- GlobalEventBus 是一种利用 Vue 实例的事件系统,实现组件间通信的方式,特别适用于兄弟组件之间的数据共享。
- 通过
this.$bus.$emit
和this.$bus.$on
可以轻松实现事件的发布和接收。 - 组件销毁前应使用
this.$bus.$off
来解绑事件,以避免内存泄漏。 - 这种模式类似于中介者模式,将事件总线作为中介者,简化了组件间的直接通信。
# 1-2 消息订阅与发布(PubSub)
消息订阅与发布是一种用于组件间通信的方式,适用于任意组件之间的通信场景。这种机制类似于观察者模式,通过一个事件中心来协调数据传递。
- 消息订阅与发布模式:通过消息的订阅和发布,实现不同组件间的数据传递。它能够有效地解耦组件,使组件之间不需要直接引用对方即可通信。
- 适用场景:适用于任意组件间通信,无论是父子组件、兄弟组件,还是不在同一层级的组件,都可以使用该模式进行通信。
# 2.1 安装 PubSub
首先,需要安装 PubSub.js 这个第三方库。可以通过 npm 进行安装:
npm install pubsub-js
# 2.2 引入 PubSub
在需要使用的组件中引入 PubSub.js:
import PubSub from 'pubsub-js';
# 2.3 订阅消息
在希望接收数据的组件中进行消息订阅。通常在组件的 mounted
钩子中进行,订阅时会得到一个订阅 ID,用于后续取消订阅:
methods: {
// 定义回调函数,接收消息和数据
handleData(msgName, data) {
console.log('接收到的消息:', msgName);
console.log('接收到的数据:', data);
}
},
mounted() {
// 订阅消息,并保存订阅ID
this.subscriptionId = PubSub.subscribe('myMessage', this.handleData);
}
2
3
4
5
6
7
8
9
10
11
- 注意事项:
- 回调函数的第一个参数是消息名称,第二个参数是传递的数据。
- 如果不需要使用某个参数,可以使用
_
占位。 - 避免直接在
subscribe
中定义匿名函数,以防this
指向不正确。推荐使用箭头函数或将回调函数写在methods
中。
# 2.4 发布消息
在需要传递数据的组件中发布消息,传递数据给订阅者:
// 发布消息,传递数据
PubSub.publish('myMessage', { key: 'value' });
2
# 2.5 取消订阅
在组件销毁之前,应该取消订阅以防止内存泄漏。通常在组件的 beforeDestroy
钩子中进行:
beforeDestroy() {
// 取消订阅,防止内存泄漏
PubSub.unsubscribe(this.subscriptionId);
}
2
3
4
注意事项
- 异步性:PubSub.js 的消息传递是异步的,有助于提高应用的响应速度。
- 解耦:使用 PubSub.js 可以使组件间的通信不需要直接引用彼此,降低了组件之间的耦合度。
- 调试:由于 PubSub.js 是第三方库,Vue 的调试工具无法监听到 PubSub.js 的通信,因此需要注意在开发过程中通过日志或其他手段进行调试。