VueJs 2.0의 형제 구성 요소 간 통신


112

개요

Vue.js 2.x에서에서 model.sync됩니다 되지 않습니다 .

그렇다면 Vue.js 2.x 에서 형제 구성 요소간에 통신하는 적절한 방법은 무엇 입니까?


배경

내가 Vue 2.x를 이해했듯이 형제 통신에 선호되는 방법 은 상점 또는 이벤트 버스를 사용하는 것 입니다.

Evan (Vue의 제작자)에 따르면 :

또한 "구성 요소간에 데이터 전달"은 일반적으로 나쁜 생각이라고 언급 할 가치가 있습니다. 결국 데이터 흐름은 추적 할 수없고 디버깅하기가 매우 어렵 기 때문입니다.

여러 구성 요소에서 데이터를 공유 해야하는 경우 글로벌 스토어 또는 Vuex를 선호합니다 .

[ 토론 링크 ]

과:

.once그리고 .sync사용되지 않습니다. 이제 소품은 항상 단방향입니다. 부모 범위에서 부작용을 생성하려면 구성 요소가 emit암시 적 바인딩에 의존하는 대신 명시 적 으로 이벤트 를 생성해야합니다 .

그래서, 에반 제안 사용 $emit()하고 $on().


우려

내가 걱정하는 것은 :

  • storeevent글로벌 가시성을 (내가 틀렸다면 정정 해줘)이있다;
  • 사소한 소통을 할 때마다 새 상점을 만드는 것은 너무 낭비입니다.

내가 원하는 것은 형제 구성 요소의 범위 events 또는 stores가시성입니다. (또는 위의 아이디어를 이해하지 못했을 수도 있습니다.)


질문

그렇다면 형제 구성 요소 간의 올바른 통신 방법은 무엇입니까?


2
$emitv-model에뮬레이트하기 위해 와 결합됩니다 .sync. 난 당신이 Vuex의 길을 가야한다고 생각합니다
eltonkamami

3
그래서 나는 같은 관심사를 고려했습니다. 내 솔루션은 '범위'와 동일한 브로드 캐스트 채널이있는 이벤트 이미 터를 사용하는 것입니다. 즉, 자식 / 부모 및 형제 설정은 동일한 채널을 사용하여 통신합니다. 제 경우에는 라디오 라이브러리 radio.uxder.com을 사용합니다. 단 몇 줄의 코드와 방탄이므로 많은 사람들이 EventEmitter 노드를 선택합니다.
Tremendus Apps 2016

답변:


83

Vue 2.0에서는 문서에 설명 된대로 eventHub 메커니즘을 사용하고 있습니다 .

  1. 중앙 집중식 이벤트 허브를 정의합니다.

    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
  2. 이제 구성 요소에서 다음을 사용하여 이벤트를 내보낼 수 있습니다.

    this.eventHub.$emit('update', data)
  3. 그리고 당신은

    this.eventHub.$on('update', data => {
    // do your thing
    })

업데이트 더 간단한 솔루션을 설명하는 @alex 의 답변을 참조하십시오 .


3
알림 : 글로벌 믹스 인주시 하고 가능한 한 피하십시오.이 링크 vuejs.org/v2/guide/mixins.html#Global-Mixin 에 따르면 타사 구성 요소에도 영향을 미칠 수 있습니다.
Vini.g.fer 2010 년

6
훨씬 더 간단한 해결책은 this.$root.$emit()this.$root.$on()
@Alex가

5
향후 참조를 위해 다른 사람의 답변으로 답변을 업데이트하지 마십시오 (더 낫다고 생각하고 참조하는 경우에도). 대체 답변에 연결하거나 OP에 다른 답변을 수락하도록 요청하세요.하지만 답변을 자신의 답변에 복사하는 것은 잘못된 형식이며 사용자가 자신의 답변 만 찬성 할 수 있으므로 사용자가 필요한 곳에 크레딧을 제공하지 못하게합니다. 대답 만하십시오. 해당 답변을 자신의 답변에 포함하지 않음으로써 참조하는 답변으로 이동하여 찬성하도록 유도하십시오.
GrayedFox

4
귀중한 피드백 @GrayedFox에 감사 드리며 그에 따라 제 답변을 업데이트했습니다.
kakoni

2
이 솔루션은 Vue 3에서 더 이상 지원되지 않습니다. stackoverflow.com/a/60895076/752916
AlexMA

145

더 짧게 만들고 루트 Vue 인스턴스를 전역 Event Hub로 사용할 수도 있습니다 .

성분 1 :

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

성분 2 :

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

2
이것은 추가 이벤트 허브를 정의하고 이벤트 소비자에게 연결하는 것보다 더 효과적입니다.
schad

2
나는 범위가있는 이벤트를 정말로 좋아하지 않기 때문에이 솔루션의 열렬한 팬입니다. 그러나 나는 매일 VueJS를 사용하지 않으므로이 접근 방식에 문제가있는 사람이 있는지 궁금합니다.
Webnet

2
모든 해답의 간단한 솔루션
Vikash 굽타

1
짧고 쉬운 좋은이뿐만 아니라, 이해하기 쉽고, 구현
나다

1
독점적으로 직접적인 형제 자매 통신 만 원할 경우 $ root 대신 $ parent 사용
Malkev

47

커뮤니케이션 유형

Vue 응용 프로그램 (또는 실제로 모든 구성 요소 기반 응용 프로그램)을 디자인 할 때 우리가 처리하는 문제에 따라 다른 통신 유형이 있으며 고유 한 통신 채널이 있습니다.

비즈니스 로직 : 앱과 그 목표와 관련된 모든 것을 나타냅니다.

프리젠 테이션 로직 : 사용자가 상호 작용하거나 사용자의 상호 작용으로 인해 발생하는 모든 것.

이 두 가지 문제는 다음 유형의 의사 소통과 관련이 있습니다.

  • 신청 상태
  • 부모-자식
  • 자녀-부모
  • 형제 자매

각 유형은 올바른 통신 채널을 사용해야합니다.


커뮤니케이션 채널

채널은 Vue 앱 주변에서 데이터를 교환하기 위해 구체적인 구현을 참조하는 데 사용할 느슨한 용어입니다.

소품 : 부모-자식 프레젠테이션 논리

직접 부모-자녀 커뮤니케이션을 위한 Vue의 가장 간단한 커뮤니케이션 채널입니다 . 주로 프리젠 테이션 논리 또는 제한된 데이터 집합과 관련된 데이터를 계층 구조 아래로 전달하는 데 사용해야합니다.

참조 및 방법 : 프레젠테이션 안티 패턴

자식이 부모의 이벤트를 처리하도록 소품을 사용하는 것이 이치에 맞지 않을 때 자식 구성 요소에를 설정하고 ref해당 메서드를 호출하는 것이 좋습니다.

그러지 마세요, 안티 패턴입니다. 구성 요소 아키텍처 및 데이터 흐름을 재고하십시오. 부모의 자식 구성 요소에 대한 메서드를 호출하려는 경우 상태를 높이 거나 여기 또는 다른 답변에 설명 된 다른 방법을 고려할 때입니다.

이벤트 : 자식-부모 프레젠테이션 논리

$emit$on. 직접적인 자녀-부모 의사 소통을위한 가장 간단한 의사 소통 채널. 다시 말하지만, 표현 로직에 사용되어야합니다.

이벤트 버스

대부분의 답변은 멀리 떨어진 구성 요소 또는 실제로 사용할 수있는 통신 채널 중 하나 인 이벤트 버스에 대한 좋은 대안을 제공합니다.

이 기능은 그 사이에 필요한 다른 컴포넌트가 거의없는 상태에서 아래로 깊숙이 중첩 된 하위 컴포넌트로 소품을 사방에 전달할 때 유용 할 수 있습니다. 신중하게 선택한 데이터에 대해서는 조금만 사용하십시오.

주의 : 이벤트 버스에 자신을 바인딩하는 구성 요소의 후속 생성은 두 번 이상 바인딩되어 여러 처리기가 트리거되고 누수가 발생합니다. 개인적으로 과거에 디자인 한 모든 단일 페이지 앱에서 이벤트 버스의 필요성을 느끼지 못했습니다.

다음 Item은 DOM에서 제거 된 경우에도 구성 요소가 계속 트리거 되는 간단한 실수로 인해 누출이 발생하는 방법을 보여줍니다 .

destroyed라이프 사이클 후크 에서 리스너를 제거하는 것을 잊지 마십시오.

중앙 집중식 저장소 (비즈니스 로직)

Vuex상태 관리를 위해 Vue를 사용하는 방법 입니다. 이벤트 이상을 제공하며 본격적인 적용을위한 준비가되어 있습니다.

이제 묻습니다 .

사소한 커뮤니케이션마다 vuex 스토어를 만들어야하나요?

다음과 같은 경우에 정말 빛납니다.

  • 비즈니스 로직을 다루고,
  • 백엔드 (또는 로컬 저장소와 같은 데이터 지속성 레이어)와 통신

따라서 구성 요소는 사용자 인터페이스를 관리하는 원래 의도에 집중할 수 있습니다.

구성 요소 논리에 사용할 수 없다는 의미는 아니지만 필요한 전역 UI 상태 만있는 네임 스페이스가있는 Vuex 모듈로 해당 논리의 범위를 지정합니다.

전역 상태의 모든 것을 처리하지 않으려면 저장소를 여러 네임 스페이스 모듈로 분리해야합니다.


구성 요소 유형

이러한 모든 통신을 조율하고 재사용을 용이하게하려면 구성 요소를 두 가지 유형으로 생각해야합니다.

  • 앱별 컨테이너
  • 일반 구성 요소

다시 말하지만, 일반 구성 요소를 재사용해야하거나 앱 특정 컨테이너를 재사용 할 수 없다는 의미는 아니지만 책임이 다릅니다.

앱별 컨테이너

이들은 다른 Vue 구성 요소 (일반 또는 기타 앱 특정 컨테이너)를 래핑하는 단순한 Vue 구성 요소입니다. 여기서 Vuex 스토어 통신이 발생하고이 컨테이너는 소품 및 이벤트 리스너와 같은 다른 간단한 수단을 통해 통신해야합니다.

이러한 컨테이너에는 기본 DOM 요소가 전혀 없을 수도 있으며 일반 구성 요소가 템플릿 및 사용자 상호 작용을 처리 할 수 ​​있습니다.

범위를 어떻게 든 events또는 stores형제 자매 구성 요소에 대한 가시성

여기에서 범위 지정이 발생합니다. 대부분의 구성 요소는 상점에 대해 알고하지 않으며,이 구성 요소는 (대부분) 사용 하나의 제한된으로 저장 모듈을 네임 스페이스해야 getters하고 actions제공된 적용 Vuex 바인딩 도우미 .

일반 구성 요소

이들은 소품에서 데이터를 수신하고, 자체 로컬 데이터를 변경하고, 간단한 이벤트를 생성해야합니다. 대부분의 경우 그들은 Vuex 스토어가 존재한다는 사실을 전혀 몰라 야합니다.

다른 UI 구성 요소에 대한 유일한 책임이있을 수 있으므로 컨테이너라고도합니다.


형제 커뮤니케이션

그렇다면이 모든 과정에서 두 형제 구성 요소간에 어떻게 통신해야합니까?

예를 들어 이해하기가 더 쉽습니다. 입력 상자가 있고 그 데이터가 앱 전체 (트리의 다른 위치에있는 형제)에서 공유되고 백엔드와 유지되어야한다고 가정합니다.

최악의 시나리오 부터 시작하여 구성 요소는 프레젠테이션비즈니스 로직을 혼합 합니다 .

// 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 및 백엔드에 대해 알 필요가 없습니다.


3
"있는 방법에 대한 의견에 동의 하는 소품 사용의 같은 커플 링 "
ghybs을

나는이 대답을 좋아한다. 하지만 이벤트 버스에 대해 자세히 설명해 주시겠습니까? "주의 :"참고? 아마도 당신은 몇 가지 예를들 수 있습니다. 나는 구성 요소가 어떻게 두 번 바인딩 될 수 있는지 이해하지 못합니다.
vandroid

양식 유효성 검사와 같이 부모 구성 요소와 그랜드 자식 구성 요소간에 어떻게 통신합니까? 부모 구성 요소는 페이지, 자식은 양식, 그랜드 자식은 입력 양식 요소입니까?
Lord Zed

1
@vandroid이 스레드의 모든 예제처럼 리스너가 제대로 제거되지 않았을 때 누수를 보여주는 간단한 예제를 만들었습니다.
Emile Bergeron 2018 년

@LordZed 정말 상황에 따라 다르지만 귀하의 상황을 이해하면 디자인 문제처럼 보입니다. Vue는 주로 프레젠테이션 로직에 사용되어야합니다. 양식 유효성 검사는 Vuex 작업이 양식의 데이터로 호출하는 바닐라 JS API 인터페이스와 같이 다른 곳에서 수행되어야합니다.
에밀 버 게론

10

좋아요, 우리는 v-on이벤트를 사용하여 부모를 통해 형제들간에 통신 할 수 있습니다.

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

Details에서 일부 요소를 클릭 할 때 업데이트 구성 요소를 원한다고 가정 해 보겠습니다 List.


에서 Parent:

주형:

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

여기:

  • v-on:select-itemList컴포넌트 에서 호출되는 이벤트입니다 (아래 참조).
  • 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뷰로 보낼 것입니다.

5

특히 .sync더 이상 사용되지 않는 Vue의 일반적인 통신 패턴을 "해킹"하려면 일반적으로 구성 요소 간의 통신을 처리하는 간단한 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대신 비슷한 것을 사용하십시오.


1
vuex이미을 사용 하고 있지만, 사소한 커뮤니케이션마다 vuex 스토어를 만들어야합니까?
Sergei Panfilov

이 정도의 정보로 말하기는 어렵지만 이미 vuexyes를 사용하고 있다면 그것을 사용 하십시오. 그걸 써.
Hector Lorenzo

1
사실은 내가 ... 우리가 각각의 작은 통신 vuex 사용할 필요가 있음을 동의 것
빅터

물론 아닙니다. 모든 것은 상황에 따라 다릅니다. 실제로 내 대답은 vuex에서 멀어집니다. 반면에 vuex와 중앙 상태 객체의 개념을 더 많이 사용할수록 객체 간의 통신에 덜 의존한다는 것을 발견했습니다. 그러나 예, 동의합니다.
Hector Lorenzo

3

형제 자매 간의 의사 소통 방법은 상황에 따라 다릅니다. 하지만 먼저 Vue 3에서 글로벌 이벤트 버스 접근 방식이 사라질 것임을 강조하고 싶습니다 . 이 RFC를 참조하십시오 . 그래서 나는 새로운 답을 쓰기로 결정했습니다.

최저 공통 조상 패턴 (또는 "LCA")

간단한 경우에는 Lowest Common Ancestor 패턴 ( "데이터 다운, 이벤트 업"이라고도 함)을 사용하는 것이 좋습니다. 이 패턴은 읽기, 구현, 테스트 및 디버그하기 쉽습니다.

본질적으로 이는 두 구성 요소가 통신해야하는 경우 공유 상태를 둘 다 조상으로 공유하는 가장 가까운 구성 요소에 두는 것을 의미합니다. 소품을 통해 부모 구성 요소에서 자식 구성 요소로 데이터를 전달하고 이벤트를 생성하여 자식에서 부모로 정보를 전달합니다 (이 답변 하단의 예제 참조).

인위적인 예의 경우, 이메일 앱에서 "받는 사람"구성 요소가 "메시지 본문"구성 요소와 상호 작용해야하는 경우 해당 상호 작용의 상태는 부모 (라는 구성 요소 일 수 있음)에있을 수 있습니다 email-form. 메시지 본문이 자동으로 앞에 추가 될 수 있도록 email-form호출에 소품이있을 수 있습니다.addresseeDear {{addressee.name}}수신자의 이메일 주소에 따라 이메일 .

LCA는 통신이 많은 중개자 구성 요소와 함께 장거리를 이동해야하는 경우 부담이됩니다. 나는 종종 동료들 에게이 훌륭한 블로그 게시물을 추천 합니다. (예제가 Ember를 사용한다는 사실은 무시하십시오. 그 아이디어는 많은 UI 프레임 워크에 적용 가능합니다.)

데이터 컨테이너 패턴 (예 : Vuex)

부모-자식 의사 소통에 너무 많은 중개자가 관여하는 복잡한 경우 또는 상황의 경우 Vuex 또는 이와 동등한 데이터 컨테이너 기술을 사용하십시오. 적절한 경우 네임 스페이스 모듈 사용 .

예를 들어 모든 기능을 갖춘 달력 구성 요소와 같이 상호 연결이 많은 복잡한 구성 요소 모음에 대해 별도의 네임 스페이스를 만드는 것이 합리적 일 수 있습니다.

발행 / 구독 (이벤트 버스) 패턴

이벤트 버스 (또는 "게시 / 구독") 패턴이 귀하의 요구에 더 적합하다면 Vue 핵심 팀은 이제 mitt 와 같은 타사 라이브러리 사용을 권장 합니다. (1 항에 언급 된 RFC를 참조하십시오.)

보너스 램 블링 및 코드

여기에서 게임을 통해 도시 동위 대 동위 통신 최저 공통 조상 용액의 기본적인 예이다 두더지 .

순진한 접근 방식은 "두더지 1이 두더지 2가 구타당한 후 나타나도록 지시해야합니다"라고 생각하는 것일 수 있습니다. 그러나 Vue는 트리 구조의 관점에서 생각하기를 원하기 때문에 이런 종류의 접근을 권장하지 않습니다 .

이것은 매우 좋은 일입니다. 노드가 DOM 트리를 통해 서로 직접 통신하는 사소한 앱은 일종의 회계 시스템 (Vuex가 제공하는 것과 같은) 없이는 디버그하기가 매우 어렵습니다. 또한 "데이터 다운, 이벤트 업"을 사용하는 구성 요소는 낮은 커플 링과 높은 재사용 성을 나타내는 경향이 있습니다. 둘 다 대규모 앱 확장에 도움이되는 매우 바람직한 특성입니다.

이 예에서는 두더지가 때리면 이벤트를 내 보냅니다. 게임 관리자 구성 요소는 앱의 새로운 상태를 결정하므로 형제 두더지는 Vue가 다시 렌더링 된 후 암시 적으로 수행 할 작업을 알고 있습니다. 이것은 다소 사소한“가장 낮은 공통 조상”의 예입니다.

Vue.component('whack-a-mole', {
  data() {
    return {
      stateOfMoles: [true, false, false],
      points: 0
    }
  },
  template: `<div>WHACK - A - MOLE!<br/>
    <a-mole :has-mole="stateOfMoles[0]" v-on:moleMashed="moleClicked(0)"/>
    <a-mole :has-mole="stateOfMoles[1]"  v-on:moleMashed="moleClicked(1)"/>
    <a-mole :has-mole="stateOfMoles[2]" v-on:moleMashed="moleClicked(2)"/>
    <p>Score: {{points}}</p>
</div>`,
  methods: {
    moleClicked(n) {
      if(this.stateOfMoles[n]) {
         this.points++;
         this.stateOfMoles[n] = false;
         this.stateOfMoles[Math.floor(Math.random() * 3)] = true;
      }   
    }
  }
})

Vue.component('a-mole', {
  props: ['hasMole'],
  template: `<button @click="$emit('moleMashed')">
      <span class="mole-button" v-if="hasMole">🐿</span><span class="mole-button" v-if="!hasMole">🕳</span>
    </button>`
})

var app = new Vue({
  el: '#app',
  data() {
    return { name: 'Vue' }
  }
})
.mole-button {
  font-size: 2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <whack-a-mole />
</div>

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.