Communication between sibling components in VueJs 2.0

◇◆丶佛笑我妖孽 提交于 2019-11-26 02:06:15

问题


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.

[Link to discussion]

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 explicitly emit an event instead of relying on implicit binding.

(So, he suggest is to use $emit and $on)

I\'m worried because of:

  • Each store and event 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?


回答1:


With Vue 2.0, I'm using the eventHub mechanism as demonstrated in the documentation.

  1. Define centralized event hub.

    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
    
  2. Now in your component you can emit events with

    this.eventHub.$emit('update', data)
    
  3. 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.




回答2:


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);
    });
}



回答3:


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 or stores 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.




回答4:


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 in List component (see below);
  • setSelectedItem it's a Parent's method to update selectedModel;

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 via select-item directly in parent. And parent will send it to the Details view



回答5:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!