首页 文章

VueJs 2.0中兄弟组件之间的通信

提问于
浏览
56

在vuejs 2.0 model.sync 将是deprecated .

那么,在vuejs 2.0中兄弟组件之间进行通信的正确方法是什么?

因为我在Vue 2.0中捕获 the idea 是为了兄弟姐妹的沟通 by using a store or an event bus .

根据evan

值得一提的是“在组件之间传递数据”通常是一个坏主意,因为最终数据流变得无法跟踪并且很难调试 . 如果一个数据需要由多个组件共享,则更喜欢全局存储或Vuex .

[Link to discussion]

和:

.once和.sync已弃用 . 道具现在总是单向下降 . 要在父作用域中产生副作用,组件需要显式发出事件而不是依赖于隐式绑定 .

(所以,他suggest是使用 $emit$on

我很担心因为:

  • 每个 storeevent 都具有全局可见性(如果我错了,请纠正我);

  • 为每个次要的沟通创建一个新的商店是很重要的;

我想要的是 scope 以某种方式 eventsstores 兄弟姐妹组件的可见性 . 或者也许我没有 grab 这个想法 .

那么,如何以正确的方式沟通?

5 回答

  • 23

    使用Vue 2.0,我正在使用documentation中演示的eventHub机制 .

    • 定义集中式事件中心 .
    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
    
    • 现在,您可以在组件中发出事件
    this.eventHub.$emit('update', data)
    
    • 听你做
    this.eventHub.$on('update', data => {
    // do your thing
    })
    

    Update 使用 $root 作为事件中心有很好的技巧(如@alex所述) .

    /// on emitter
        this.$root.$emit(...)
    
        // on listener
        this.$root.$on(...)
    
  • 47

    您甚至可以缩短它并使用root Vue 实例作为全局事件中心:

    第1部分:

    this.$root.$emit('eventing', data);
    

    第2部分:

    mounted() {
        this.$root.$on('eventing', data => {
            console.log(data);
        });
    }
    
  • 53

    我知道这是一个老问题,但我想揭露其他沟通渠道以及如何从更高的角度查看应用和通信 .


    通讯类型

    在设计Vue应用程序(或实际上是任何基于组件的应用程序)时要首先理解的是,有不同的通信类型取决于我们正在处理的问题,并且它们需要自己的通信通道 .

    Business logic: 指的是特定于您的应用及其目标的所有内容 .

    Presentation logic: 用户与之交互或用户交互产生的任何内容 .

    这两个问题与这些类型的沟通有关:

    • 申请状态

    • 亲子

    • 亲子

    • 兄弟姐妹

    每种类型都应使用正确的通信渠道 .


    通讯渠道

    一个 Channels 是一个松散的术语,我将用它来指代围绕Vue应用程序交换数据的具体实现 .

    道具(演示逻辑)

    Vue中最简单的通信通道,用于直接的父子通信 . 它主要用于传递与表示逻辑有关的数据或在层次结构中传递受限制的数据集 .

    参考和方法(表示逻辑)

    当使用道具让孩子处理来自父母的事件没有意义时,_1254105就可以了 .

    有些人可能会说这是父母和孩子之间的紧密耦合,但它与使用道具的耦合相同 . 如果我们可以就道具 Contract 达成一致,我们也可以就方法 Contract 达成一致 .

    事件(演示逻辑)

    $emit$on . 直接的儿童 - 家长沟通的最简单的沟通渠道 . 同样,应该用于表示逻辑 .

    活动 Bus (两者)

    大多数答案为事件总线提供了很好的替代方案,事件总线是远程组件可用的通信通道之一,或者事实上的任何东西 .

    当将道具从远处向上传递到深层嵌套的子组件时,这可以变得有用,几乎没有其他组件需要这些组件 .

    注意:后续创建绑定到事件总线的组件将被绑定多次 - 导致多个处理程序被触发和泄漏 . 在我过去设计的所有单页应用程序中,我个人从未觉得需要事件总线 .

    下面演示了一个简单的错误如何导致泄漏,即使从DOM中删除了 Item 组件仍会触发 .

    // 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.

    集中存储(业务逻辑)

    Vuex是Vue for state management 的方法 . 它提供的不仅仅是事件,它已经准备好进行全面的应用 .

    现在you ask

    [S]我应该为每次小型沟通创建vuex的商店吗?

    它真的很棒:

    • 处理您的业务逻辑,

    • 与后端通信

    因此,您的组件可以真正专注于他们想要的事情,管理用户界面 .

    这并不意味着您不能将它用于组件逻辑,但我会将该逻辑范围限定为仅具有必要全局UI状态的命名空间Vuex模块 .

    为了避免处理全局状态中的所有内容,我们应该将存储拆分为多个命名空间模块 .


    组件类型

    为了协调所有这些通信并简化可重用性,我们应该将组件视为两种不同的类型 .

    • App特定容器

    • 通用组件

    同样,它并不意味着应该重用通用组件或者不能重用特定于应用程序的容器,但它们具有不同的职责 .

    App特定容器

    这些只是简单的Vue组件,它包装其他Vue组件(通用或其他特定于应用程序的容器) . 这是Vuex商店通信应该发生的地方,这个容器应该通过其他更简单的方式,如道具和事件监听器进行通信 .

    这些容器甚至可以根本没有本机DOM元素,让通用组件处理这个问题 .

    范围某种方式事件或存储兄弟姐妹组件的可见性

    这是范围发生的地方 . 大多数组件都不知道存储,这个组件应该(大多数)使用一个命名空间存储模块,并使用提供的Vuex映射器应用一组有限的 gettersactions .

    通用组件

    这些应该从props接收数据,对自己的本地数据进行更改,并发出简单的事件 . 大多数时候,他们不应该知道Vuex商店存在 .

    它们也可以称为容器,因为它们唯一的责任可能是调度到其他UI组件 .


    兄弟姐妹的沟通

    那么,在这之后,我们应该如何在两个兄弟组件之间进行通信?

    通过一个例子更容易理解:说我们有一个输入框,它的数据应该在应用程序(树中不同位置的兄弟姐妹)之间共享,并持有后端 .

    worst case scenario 开始,我们的组件将混合表示和业务逻辑 .

    // 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>
    

    为了区分这两个问题,我们应该将我们的组件包装在特定于应用程序的容器中,并将表示逻辑保存到我们的通用输入组件中

    我们的输入组件现在可以重复使用,并且不了解后端和兄弟姐妹 .

    // 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>
    

    我们的应用程序专用容器现在可以成为业务逻辑和表示通信之间的桥梁 .

    // 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>
    

    由于Vuex存储操作处理后端通信,因此我们的容器不需要知道axios和后端 .

  • 5

    好的,我们可以使用 v-on 事件通过父级在兄弟姐妹之间进行通信 .

    Parent
     |-List of items //sibling 1 - "List"
     |-Details of selected item //sibling 2 - "Details"
    

    假设我们在 List 中单击某个元素时需要更新 Details 组件 .


    Parent

    模板:

    <list v-model="listModel"
          v-on:select-item="setSelectedItem" 
    ></list> 
    <details v-model="selectedModel"></details>
    

    这里:

    • v-on:select-item 这是一个事件,将在 List 组件中调用(见下文);

    • setSelectedItem 这是 Parent 更新 selectedModel 的方法;

    JS:

    //...
    data () {
      return {
        listModel: ['a', 'b']
        selectedModel: null
      }
    },
    methods: {
      setSelectedItem (item) {
        this.selectedModel = item //here we change the Detail's model
      },
    }
    //...
    

    List

    模板:

    <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"
      },
    }
    //...
    

    这里:

    • this.$emit('select-item', item) 将直接通过 select-item 在父项中发送项目 . 父母将其发送到 Details 视图
  • 9

    如果我想在Vue中正常的通信模式,特别是现在不推荐使用 .sync ,我通常会做的是创建一个处理组件之间通信的简单EventEmitter . 来自我最近的一个项目:

    import {EventEmitter} from 'events'
    
    var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
    

    使用此 Transmitter 对象,您可以在任何组件中执行此操作:

    import Transmitter from './Transmitter'
    
    var ComponentOne = Vue.extend({
      methods: {
        transmit: Transmitter.emit('update')
      }
    })
    

    并创建一个“接收”组件:

    import Transmitter from './Transmitter'
    
    var ComponentTwo = Vue.extend({
      ready: function () {
        Transmitter.on('update', this.doThingOnUpdate)
      }
    })
    

    同样,这是真正特定的用途 . 不要将整个应用程序基于此模式,而是使用类似 Vuex 的内容 .

相关问题