In vuejs 2.0 model.sync
will be deprecated.
So, what is a proper way to communicate between sibling components in vuejs 2.0?
As I catch the idea in Vue 2.0 is to have sibling communication by using a store or an event bus.
According to evan:
It's also worth mentioning "passing data between components" is generally a bad idea, because in the end the data flow becomes untrackable and very hard to debug.
If a piece of data needs to be shared by multiple components, prefer global stores or Vuex.
And:
.once
and.sync
are deprecated. Props are now always one-way down. To produce side effects in the parent scope, a component needs to explicitlyemit
an event instead of relying on implicit binding.
(So, he suggest is to use $emit
and $on
)
I'm worried because of:
- Each
store
andevent
has a global visibility (correct me if I'm wrong); - It's to much to create a new store for each minor communication;
What I want is to scope somehow events
or stores
visibility for siblings components. Or perhaps I didn't catch the idea.
So, how communicate in a right way?
With Vue 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.
Define centralized event hub.
const eventHub = new Vue() // Single event hub // Distribute to components using global mixin Vue.mixin({ data: function () { return { eventHub: eventHub } } })
Now in your component you can emit events with
this.eventHub.$emit('update', data)
And to listen you do
this.eventHub.$on('update', data => { // do your thing })
Update Please see the anwer by @alex, which describes more simpler solution.
You can even make it shorter and use root Vue
instance as global Event Hub:
Component 1:
this.$root.$emit('eventing', data);
Component 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
I know this is an old question, but I wanted to expose other communication channels and how to view the app and communications from a higher perspective.
Communication types
The first thing to understand when designing a Vue application (or in fact, any component based application) is that there are different communication types that depends on which concerns we're dealing with and they need their own communication channels.
Business logic: refers to everything specific to your app and its goal.
Presentation logic: anything that the user interacts with or that results from the interaction from the user.
These two concerns are related to these types of communication:
- Application state
- Parent-child
- Child-parent
- Siblings
Each type should use the right communication channel.
Communication channels
A channel is a loose term I'll be using to refer to concrete implementations to exchange data around a Vue app.
Props (Presentation logic)
The simplest communication channel in Vue for direct Parent-Child communication. It should mostly be used to pass data relating to presentation logic or a restricted set of data down the hierarchy.
Refs and methods (Presentation logic)
When it doesn't make sense to use a prop to let a child handle an event from a parent, setting up a ref
on the child component and calling its methods is just fine.
Some people may say that this is a tight coupling between the parent and the child, but it's the same coupling that of using props. If we can agree on a contract for props, we can agree on a contract for methods as well.
Events (Presentation logic)
$emit
and $on
. The simplest communication channel for direct Child-Parent communication. Again, should be used for presentation logic.
Event bus (Both)
Most answers give good alternatives for event bus, which is one of the communication channels available for distant components, or anything in fact.
This can become useful when passing props all over the place from far up down to deeply nested children components, with almost no other components needing these in between.
Be careful: Subsequent creation of components that are binding themselves to the event bus will be bound more than once--leading to multiple handlers triggered and leaks. I personally never felt the need for an event bus in all the single page apps I've designed in the past.
The following demonstrates how a simple mistake leads to a leak where the Item
component still triggers even if removed from the DOM.
// A component that binds to a custom 'update' event.
var Item = {
template: `<li>{{text}}</li>`,
props: {
text: Number
},
mounted() {
this.$root.$on('update', () => {
console.log(this.text, 'is still alive');
});
},
};
// Component that emits events
var List = new Vue({
el: '#app',
components: {
Item
},
data: {
items: [1, 2, 3, 4]
},
updated() {
this.$root.$emit('update');
},
methods: {
onRemove() {
console.log('slice');
this.items = this.items.slice(0, -1);
}
}
});
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
<div id="app">
<button type="button" @click="onRemove">Remove</button>
<ul>
<item v-for="item in items" :key="item" :text="item"></item>
</ul>
</div>
Remember to remove listeners in the destroyed
lifecycle hook.
Centralized store (Business logic)
Vuex is the way to go with Vue for state management. It offers a lot more than just events and it's ready for full scale application.
And now you ask:
[S]hould I create vuex's store for each minor communication?
It really shines when:
- dealing with your business logic,
- communicating with a backend
So your components can really focus on the things they're meant to be, managing user interfaces.
It doesn't mean that you can't use it for component logic, but I would scope that logic to a namespaced Vuex module with only the necessary global UI state.
To avoid dealing with a big mess of everything in a global state, we should split the store in multiple namespaced modules.
Component types
To orchestrates all these communications and to ease reusability, we should think of components as two different types.
- App specific containers
- Generic components
Again, it doesn't mean that a generic component should be reused or that an app specific container can't be reused, but they have different responsibilities.
App specific containers
These are just simple Vue component that wraps other Vue components (generic or other app specific containers). This is where the Vuex store communication should happen and this container should communicate through other simpler means like props and event listeners.
These containers could even have no native DOM elements at all and let the generic components deal with this.
scope somehow
events
orstores
visibility for siblings components
This is where the scoping happens. Most components don't know about the store and this component should (mostly) use one namespaced store module with a limited set of getters
and actions
applied with the provided Vuex mappers.
Generic components
These should receive their data from props, make changes on their own local data, and emit simple events. Most of the time, they should not know a Vuex store exists.
They could also be called containers as their sole responsibility could be to dispatch to other UI components.
Sibling communication
So, after all this, how should we communicate between two sibling components?
It's easier to understand with an example: say we have an input box and its data should be shared across the app (siblings at different places in the tree) and persisted with a backend.
Starting with the worst case scenario, our component would mix presentation and business logic.
// MyInput.vue
<template>
<div class="my-input">
<label>Data</label>
<input type="text"
:value="value"
:input="onChange($event.target.value)">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
value: "",
};
},
mounted() {
this.$root.$on('sync', data => {
this.value = data.myServerValue;
});
},
methods: {
onChange(value) {
this.value = value;
axios.post('http://example.com/api/update', {
myServerValue: value
})
.then((response) => {
this.$root.$emit('update', response.data);
});
}
}
}
</script>
To separate these two concerns, we should wrap our component in an app specific container and keep the presentation logic into our generic input component.
Our input component is now reusable and doesn't know about the backend nor the siblings.
// MyInput.vue
// the template is the same as above
<script>
export default {
props: {
initial: {
type: String,
default: ""
}
},
data() {
return {
value: this.initial,
};
},
methods: {
onChange(value) {
this.value = value;
this.$emit('change', value);
}
}
}
</script>
Our app specific container can now be the bridge between the business logic and the presentation communication.
// MyAppCard.vue
<template>
<div class="container">
<card-body>
<my-input :initial="serverValue" @change="updateState"></my-input>
<my-input :initial="otherValue" @change="updateState"></my-input>
</card-body>
<card-footer>
<my-button :disabled="!serverValue || !otherValue"
@click="saveState"></my-button>
</card-footer>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import { NS, ACTIONS, GETTERS } from '@/store/modules/api';
import { MyButton, MyInput } from './components';
export default {
components: {
MyInput,
MyButton,
},
computed: mapGetters(NS, [
GETTERS.serverValue,
GETTERS.otherValue,
]),
methods: mapActions(NS, [
ACTIONS.updateState,
ACTIONS.updateState,
])
}
</script>
Since the Vuex store actions deal with the backend communication, our container here doesn't need to know about axios and the backend.
Okay, we can communicate between siblings via parent using v-on
events.
Parent
|-List of items //sibling 1 - "List"
|-Details of selected item //sibling 2 - "Details"
Let's assume that we want update Details
component when we click some element in List
.
in Parent
:
Template:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Here:
v-on:select-item
it's an event, that will be called inList
component (see below);setSelectedItem
it's aParent
's method to updateselectedModel
;
JS:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item //here we change the Detail's model
},
}
//...
In List
:
Template:
<ul>
<li v-for="i in list"
:value="i"
@click="select(i, $event)">
<span v-text="i"></span>
</li>
</ul>
JS:
//...
data () {
return {
selected: null
}
},
props: {
list: {
type: Array,
required: true
}
},
methods: {
select (item) {
this.selected = item
this.$emit('select-item', item) // here we call the event we waiting for in "Parent"
},
}
//...
Here:
this.$emit('select-item', item)
will send item viaselect-item
directly in parent. And parent will send it to theDetails
view
What I usually do if I want to "hack" the normal patterns of communication in Vue, specially now that .sync
is deprecated, is to create a simple EventEmitter that handles communication between components. From one of my latest projects:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
With this Transmitter
object you can then do, in any component:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
And to create a "receiving" component:
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Again, this is for really specific uses. Don't base your whole application on this pattern, use something like Vuex
instead.
来源:https://stackoverflow.com/questions/38616167/communication-between-sibling-components-in-vuejs-2-0