ref和通信
ref
在 Vue 组件中,ref
是一个用于在模板或组件内部获取对 DOM 元素或子组件实例的引用的特殊属性。ref
的使用场景和功能包括:
- 访问和操作 DOM 元素:通过给元素添加
ref
属性,可以在组件中直接访问和操作该 DOM 元素的属性、样式、以及调用其方法。
- 与子组件进行通信:通过给子组件添加
ref
属性,可以在父组件中获取子组件的实例,并通过实例调用子组件的方法、访问其属性。
下面是一个完整的示例,展示了 ref
的使用场景和示例:
1 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
| <template> <div> <h1 ref="title">方式1:DOM元素</h1> <button @click="changeTitle">更改title</button> <child-component ref="child1">方式2: 引用子组件实例</child-component> <child-component ref="child2"></child-component> </div> </template>
<script> import ChildComponent from './ChildComponent.vue';
export default { components: { ChildComponent, }, methods: { changeTitle() { // 访问和修改 DOM 元素 this.$refs.title.innerText = 'New Title';
// 通过子组件的实例进行通信 this.$refs.child1.methodInChild(); this.$refs.child2.methodInChild(); }, }, }; </script>
|
在上面的示例中,我们首先通过给 <h1>
元素添加 ref="title"
,在组件中创建了一个对该元素的引用。然后,我们通过点击按钮触发 changeTitle
方法,在方法中使用 this.$refs.title
访问并修改了该元素的内容。
另外,我们在模板中使用了 <child-component>
组件,并给它添加了 ref="child1"
。在 changeTitle
方法中,我们通过 this.$refs.child1
获取到了子组件的实例,并调用了子组件的 methodInChild
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> <h2>Child Component</h2> <button @click="methodInChild">Child Method</button> </div> </template>
<script> export default { methods: { methodInChild() { console.log('Child method called'); }, }, }; </script>
|
需要注意的是,ref
的作用域限定在当前组件中,无法在子组件中通过父组件的 $refs
访问其他子组件。此外,使用 ref
的组件或元素必须在渲染完成后才能被访问,所以通常在 mounted
生命周期钩子函数或之后的钩子函数中使用 $refs
来保证访问的时机。
父向子
- props:通过使用 props 将数据从父组件传递给子组件。子组件通过 props 接收数据并使用。
1 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
| <!-- 父组件 --> <template> <div> <ChildComponent :message="message" name="bifeng" /> </div> </template>
<script> import ChildComponent from './ChildComponent.vue';
export default { components: { ChildComponent, }, data() { return { message: 'Hello from parent!', }; }, }; </script>
<!-- 子组件 --> <template> <div> <p>{{ message }}</p> <p>My name is {{name}}</p> </div> </template>
<script> export default { props: ['message', 'name'], }; </script>
|
注意, 组件中属性的定义包含两种方式: 直接定义, 通过v-bind
定义, 实际上v-bind最终的效果也是定义一个属性, 只不过其值变为一个表达式.
- 使用
$refs
:通过给组件添加 ref
属性,可以直接访问子组件的实例, 调用子组件的方法并传递参数实现双向通信, 这是利用UML中的关联关系
- 使用
$children
和$parent
: 父组件通过使用 $children
访问子组件数组,并可以进一步访问子组件的属性或方法。在父组件的 logChildren
方法中,我们通过 this.$children
输出子组件数组,并通过 this.$children[0].message
访问子组件的 message
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div> <h2>Parent Component</h2> <button @click="logChildren">Log Children</button> <ChildComponent></ChildComponent> </div> </template>
<script> import ChildComponent from './ChildComponent.vue';
export default { components: { ChildComponent, }, methods: { logChildren() { console.log(this.$children); // 访问子组件数组 console.log(this.$children[0].message); // 访问子组件的属性或方法 }, }, }; </script>
|
子组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div> <h3>Child Component</h3> <p>{{ message }}</p> <button @click="changeMessage">Change Message</button> </div> </template>
<script> export default { data() { return { message: 'Hello from child component', }; }, methods: { changeMessage() { this.message = 'Updated message from child component'; }, }, }; </script>
|
需要注意的是,使用 $children
访问子组件是通过索引进行的,并且它只能访问直接子组件,而无法访问嵌套的子组件。此外,$children
是一个响应式属性,当子组件的数量发生变化时,它也会相应地更新
- 使用
$attrs
和 $listeners
:通过属性继承和事件绑定,实现父组件向子组件传递属性和监听事件
子向父
- emit:通过使用自定义事件和 $emit 方法,在子组件中触发事件,并将数据传递给父组件。
1 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
| <!-- 子组件 --> <template> <div> <button @click="sendMessage">Send Message to Parent</button> </div> </template>
<script> export default { methods: { sendMessage() { this.$emit('message', 'Hello from child!'); }, }, }; </script>
<!-- 父组件, 其中v-bind:message为自定义绑定的事件 --> <template> <div> <ChildComponent @message="handleMessage" /> <p>{{ receivedMessage }}</p> </div> </template>
<script> import ChildComponent from './ChildComponent.vue';
export default { components: { ChildComponent, }, data() { return { receivedMessage: '', }; }, methods: { handleMessage(message) { this.receivedMessage = message; }, }, }; </script>
|
- slot: 通过在父组件中定义插槽,子组件可以将内容插入到指定位置,实现通信和组合。
兄弟
通过使用一个共享的父组件,将数据传递给父组件,再由父组件将数据传递给其他子组件, 实际上其是复用了emit和props实现的.
1 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
| <!-- 共享的父组件 --> <template> <div> <ChildComponentA :message="message" /> <ChildComponentB :message="message" /> </div> </template>
<script> import ChildComponentA from './ChildComponentA.vue'; import ChildComponentB from './ChildComponentB.vue';
export default { components: { ChildComponentA, ChildComponentB, }, data() { return { message: 'Hello from parent!', }; }, }; </script>
<!-- 子组件 A --> <template> <div> <p>{{ message }}</p> </div> </template>
<script> export default { props: ['message'], }; </script>
<!-- 子组件 B --> <template> <div> <p>{{ message }}</p> </div> </template>
<script> export default { props: ['message'], }; </script>
|
全局通信
- 使用事件总线(Event Bus):创建一个空的 Vue 实例作为事件中心,其他组件通过该实例来触发和监听事件。
1 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
| import Vue from 'vue'; export default new Vue();
import EventBus from './EventBus.js';
export default { methods: { sendMessage() { EventBus.$emit('message', 'Hello from component A!'); }, }, };
import EventBus from './EventBus.js';
export default { data() { return { receivedMessage: '', }; }, mounted() { EventBus.$on('message', (message) => { this.receivedMessage = message; }); }, };
|
另外一个定义全局事件总线的方式, 通过原型链来实现:
1 2 3 4 5 6 7 8 9 10 11
|
import Vue from 'vue';
Vue.prototype.$bus = new Vue();
new Vue({ }).$mount('#app');
|
- 使用 Vuex(Vue 的状态管理库):在全局状态中存储和管理数据,不同组件可以通过访问共享的状态来实现通信。
订阅和发布
时间总线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!-- ComponentA.vue -->
<template> <div> <h2>Component A</h2> <button @click="emitEvent">Emit Event</button> </div> </template>
<script> export default { methods: { emitEvent() { // 触发自定义事件 this.$bus.$emit('custom-event', 'Custom event emitted from Component A'); }, }, }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!-- ComponentB.vue -->
<template> <div> <h2>Component B</h2> <p>{{ message }}</p> </div> </template>
<script> export default { data() { return { message: '', }; }, created() { // 订阅自定义事件 this.$bus.$on('custom-event', (data) => { this.message = data; }); }, }; </script>
|
在 ComponentA.vue
中,我们使用 $bus.$emit
触发了一个自定义事件,并传递了相关的数据。在 ComponentB.vue
中,我们使用 $bus.$on
订阅了这个自定义事件,并在回调函数中更新了组件的 message
数据。
通过定义全局事件总线,我们可以在任何组件中进行事件的发布和订阅,实现组件间的通信。需要注意的是,全局事件总线的缺点是它将所有的事件都集中在一个地方,可能导致事件的管理变得复杂。因此,在大型应用中,建议使用更灵活的状态管理方案,如 Vuex。
pubsub
PubSubJS 是一个简单的 JavaScript 消息发布-订阅库,用于实现组件间的解耦和消息传递。它提供了一个中央的消息中心,允许订阅者(subscribers)订阅特定的主题(topics),并在消息发布者(publishers)发布消息时接收通知。
下面是一个基于 PubSubJS 的消息订阅和通知示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
import PubSub from 'pubsub-js';
const token = PubSub.subscribe('my-topic', (topic, data) => { console.log('Received message:', data); });
PubSub.publish('my-topic', 'Hello, subscribers!');
PubSub.unsubscribe(token);
|
在上面的示例中,我们首先通过 PubSub.subscribe
订阅了名为 'my-topic'
的主题,回调函数将在该主题有消息发布时被触发。然后,我们使用 PubSub.publish
发布了一条消息到 'my-topic'
主题,所有订阅了该主题的回调函数都将接收到通知并执行相应的操作。最后,我们使用 PubSub.unsubscribe
取消了订阅。
通过使用 PubSubJS,我们可以在应用程序中实现松散耦合的组件通信,使得组件之间不直接依赖和调用彼此,而是通过订阅和发布消息进行通信。这种方式可以提高代码的可维护性和可扩展性,使得组件之间的交互更加灵活和解耦。
vuex
当使用 Vuex 进行状态管理时,可以通过共享的 Vuex 存储来实现多个组件之间的通信。下面是一个简单的示例,演示了如何在多个组件之间进行状态的读取和更新:
- 创建 Vuex 存储:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({ state: { counter: 0, }, mutations: { increment(state) { state.counter++; }, }, });
export default store;
|
- 在根组件中注册 Vuex 存储:
1 2 3 4 5 6 7 8 9 10
|
import Vue from 'vue'; import App from './App.vue'; import store from './store';
new Vue({ store, render: (h) => h(App), }).$mount('#app');
|
- 在组件中使用 Vuex 状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div> <h2>Counter: {{ counter }}</h2> <button @click="incrementCounter">Increment</button> </div> </template>
<script> export default { computed: { counter() { return this.$store.state.counter; }, }, methods: { incrementCounter() { this.$store.commit('increment'); }, }, }; </script>
|
在上面的示例中,我们首先创建了一个名为 counter
的状态,并在 mutations
中定义了 increment
方法来更新该状态。然后,在根组件中通过 new Vuex.Store()
创建了 Vuex 存储,并将其注册到根实例中。接下来,在子组件中,通过计算属性 counter
来读取 counter
状态的值,并使用 incrementCounter
方法来调用 increment
mutation 来更新状态。
通过使用 Vuex,我们可以实现多个组件之间共享的状态管理,使得组件之间的通信更加便捷和可维护。在实际应用中,可以根据需求定义不同的状态和相应的 mutations,然后在组件中读取和更新这些状态,实现组件间的数据共享和通信。
mixin
mixin 是 Vue 中一种用于复用组件选项的方式。它允许我们在多个组件之间共享相同的逻辑、方法和数据。通过 mixin,我们可以将通用的功能提取出来,然后在多个组件中混入这些功能。mixin主要包括以下几个方面:
- 代码复用:mixin 可以将通用的代码逻辑封装成可复用的部分,避免重复编写相同的代码。
- 组件选项混入:mixin 可以将其包含的选项混入到组件中,例如生命周期钩子、计算属性、方法等。
- 数据合并:当多个 mixin 应用到同一个组件时,它们的数据会被合并到组件的数据中,避免命名冲突。
使用场景:
- 当多个组件有相同的方法或逻辑时,可以将这部分代码提取为 mixin,然后在这些组件中混入该 mixin。
- 当需要在多个组件中共享一些通用的计算属性时,可以将这些计算属性定义在 mixin 中,然后在组件中混入。
- 当希望在组件中复用某些数据和方法时,可以将它们提取到 mixin 中,然后在多个组件中混入该 mixin。
下面是一个 mixin 的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
export default { data() { return { count: 0, }; }, methods: { increment() { this.count++; }, decrement() { this.count--; }, }, };
|
然后,在组件中使用 mixin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <div> <h2>Count: {{ count }}</h2> <button @click="increment">Increment</button> <button @click="decrement">Decrement</button> </div> </template>
<script> import mixin from './mixin';
export default { mixins: [mixin], }; </script>
|
在上面的示例中,我们定义了一个名为 mixin
的 mixin,它包含了一个 count
数据和 increment
、decrement
方法。然后,在组件中使用 mixins
选项将该 mixin 混入到组件中,这样组件就可以访问到 mixin 中的数据和方法。通过这种方式,我们实现了在多个组件中共享相同的数据和方法。
需要注意的是,mixin 的使用需要谨慎,因为它可能导致命名冲突和不可预测的行为。在使用 mixin 时,需要确保 mixin 中的选项和组件中的选项不会发生冲突,并且需要清楚地知道 mixin 中的选项会被混入到组件中。