Jak wywołać funkcję na komponencie potomnym na zdarzeniach nadrzędnych

164

Kontekst

W Vue 2.0 dokumentacja i inne jasno wskazują, że komunikacja od rodzica do dziecka odbywa się za pośrednictwem rekwizytów.

Pytanie

W jaki sposób rodzic może powiedzieć swojemu dziecku, że zdarzenie miało miejsce za pomocą rekwizytów?

Czy powinienem po prostu obejrzeć rekwizyt zwany wydarzeniem? To nie wydaje się właściwe, podobnie jak alternatywy ( $emit/ $onjest dla dziecka do rodzica, a model piasty jest dla odległych elementów).

Przykład

Mam kontener nadrzędny i musi on powiedzieć swojemu kontenerowi podrzędnemu, że można wykonywać określone czynności w interfejsie API. Muszę mieć możliwość wyzwalania funkcji.

jbodily
źródło

Odpowiedzi:

233

Podaj składnikowi podrzędnemu a refi użyj go, $refsaby bezpośrednio wywołać metodę w składniku podrzędnym.

html:

<div id="app">
  <child-component ref="childComponent"></child-component>
  <button @click="click">Click</button>  
</div>

javascript:

var ChildComponent = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    'child-component': ChildComponent
  },
  methods: {
    click: function() {
        this.$refs.childComponent.setValue(2.0);
    }
  }
})

Aby uzyskać więcej informacji, zobacz dokumentację Vue dotyczącą ref .

joerick
źródło
13
W ten sposób komponenty macierzyste i potomne zostają połączone. Dla prawdziwych wydarzeniach, powiedzieć, kiedy nie można po prostu zmienić podpory aby wywołać akcję, pójdę z rozwiązaniem zaproponowanym przez @Roy autobusowej J
Jared
3
odniesienie do dokumentów byłoby pomocne również vuejs.org/v2/guide/ ...
ctf0
W moim komponencie dziecięcym, ze specjalnych powodów, musiałem użyć v-raz, aby zakończyć reaktywność. Dlatego przekazanie rekwizytu z rodzica na dziecko nie wchodziło w grę, więc to rozwiązanie załatwiło sprawę!
John,
1
pytanie dla początkujących: Po co używać refzamiast tworzyć rekwizytu, który obserwuje jego wartość, a następnie emituje go do innej funkcji w rodzicu? Mam na myśli to, że ma wiele rzeczy do zrobienia, ale czy używanie jest refnawet bezpieczne? Dzięki
Irfandy Jip
5
@IrfandyJip - tak, refjest bezpieczny. Generalnie jest to odradzane, ponieważ społeczność Vue woli przekazywać stan dzieciom, a zdarzenia z powrotem rodzicom. Ogólnie rzecz biorąc, prowadzi to do bardziej odizolowanych, wewnętrznie spójnych komponentów (dobra rzecz ™). Ale jeśli informacja, którą przekazujesz dziecku, naprawdę jest zdarzeniem (lub poleceniem), modyfikowanie stanu nie jest właściwym wzorcem. W takim przypadku wywołanie metody przy użyciu a refjest całkowicie w porządku i nie spowoduje awarii ani nic takiego.
joerick
76

To, co opisujesz, to zmiana stanu rodzica. Przekazujesz to dziecku przez rekwizyt. Jak zasugerowałeś, zrobiłbyś watchten rekwizyt. Kiedy dziecko podejmuje działanie, powiadamia o tym rodzica za pomocą emit, a rodzic może wtedy ponownie zmienić stan.

Jeśli naprawdę chcesz przekazać zdarzenia dziecku, możesz to zrobić, tworząc autobus (który jest tylko instancją Vue) i przekazując go dziecku jako rekwizyt .

Roy J.
źródło
2
Myślę, że to jedyna odpowiedź zgodna z oficjalnym przewodnikiem po stylu Vue.JS i najlepszymi praktykami. Jeśli użyjesz skrótu v-modelna komponencie, możesz również łatwo zresetować wartość, emitując odpowiednie zdarzenie z mniejszą ilością kodu.
Falco
Na przykład chcę wysłać alert, gdy użytkownik kliknie przycisk. Czy proponujesz na przykład: - obserwuj flagę - ustaw tę flagę od 0 do 1, gdy nastąpi kliknięcie, - zrób coś - zresetuj flagę
Sinan Erdem
9
Jest to bardzo niewygodne, trzeba stworzyć propw dziecku ekstra, w domu dodatkową nieruchomość data, potem dodać watch... Byłoby wygodnie, gdyby było wbudowane wsparcie, które w jakiś sposób przenosi zdarzenia z rodzica na dziecko. Taka sytuacja występuje dość często.
Илья Зеленько
1
Jak stwierdza @ ИльяЗеленько, zdarza się to dość często, w tej chwili byłby to dar niebios.
Craig,
1
Dzięki @RoyJ, myślę, że to wymaga, aby rekwizyt autobusowy istniał, gdy dziecko je subskrybuje, chociaż przypuszczam, że cała idea wysyłania wydarzeń do dzieci jest odradzana w Vue.
Ben Winding
35

Możesz użyć $emiti $on. Używając kodu @RoyJ:

html:

<div id="app">
  <my-component></my-component>
  <button @click="click">Click</button>  
</div>

javascript:

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  },
  created: function() {
    this.$parent.$on('update', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
    click: function() {
        this.$emit('update', 7);
    }
  }
})

Uruchomiony przykład: https://jsfiddle.net/rjurado/m2spy60r/1/

drinor
źródło
6
Dziwię się, że to działa. Myślałem, że emisja do dziecka jest anty-wzorcem lub że intencją było, aby emisja była tylko od dziecka do rodzica. Czy są jakieś potencjalne problemy z pójściem w drugą stronę?
jbodily
2
To może nie być uważane za najlepszy sposób, nie wiem, ale jeśli wiesz, co robisz, wydaje mi się, że nie stanowi to problemu. Innym sposobem jest skorzystanie z autobusu centralnego: vuejs.org/v2/guide/ ...
drinor
14
Tworzy to powiązanie między dzieckiem a rodzicem i jest uważane za złą praktykę
morrislaptop
5
Działa to tylko dlatego, że rodzic nie jest komponentem, ale w rzeczywistości aplikacją vue. W rzeczywistości jest to użycie instancji vue jako autobusu.
Julio Rodrigues
2
@Bsienn wywołanie this. $ Parent uzależnia ten komponent od rodzica. używa $ emit do i props, więc jedyne zależności są za pośrednictwem systemu komunikacji Vue. Takie podejście umożliwia użycie tego samego komponentu w dowolnym miejscu w hierarchii komponentów.
morrislaptop
6

Jeśli masz czas, użyj sklepu Vuex do bezpośredniego oglądania zmiennych (czyli stanu) lub wyzwalania (czyli wysyłania) akcji.

Brightknight08
źródło
2
ze względu na reaktywność vuejs / vuex, która jest najlepszym podejściem, w rodzicu wykonaj akcję / mutację, która zmienia wartość właściwości vuex, aw dziecku ma obliczoną wartość, która uzyskuje tę samą vuex $ store.state.property.value lub "watch „metoda, która robi coś, gdy vuex” $ store.state.property.value się zmienia
FabianSilva
6

Nie podobało mi się podejście z magistralą zdarzeń używające $onpowiązań w dziecku podczas create. Czemu? Kolejne createwywołania (których używam vue-router) wiążą program obsługi wiadomości więcej niż raz - prowadząc do wielu odpowiedzi na wiadomość.

Nieco lepiej sprawdziło się ortodoksyjne rozwiązanie polegające na przekazywaniu rekwizytów z rodzica na dziecko i umieszczaniu w nim strażnika mienia . Jedynym problemem jest to, że dziecko może działać tylko przy zmianie wartości. Wielokrotne przekazywanie tej samej wiadomości wymaga pewnego rodzaju księgowości, aby wymusić przejście, aby dziecko mogło odebrać zmianę.

Odkryłem, że jeśli zawiniemy wiadomość w tablicę, zawsze wyzwoli to obserwator dziecka - nawet jeśli wartość pozostanie taka sama.

Rodzic:

{
   data: function() {
      msgChild: null,
   },
   methods: {
      mMessageDoIt: function() {
         this.msgChild = ['doIt'];
      }
   }   
   ...
}

Dziecko:

{
   props: ['msgChild'],
   watch: {
      'msgChild': function(arMsg) {
         console.log(arMsg[0]);
      }
   }
}

HTML:

<parent>
   <child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
Jason Stewart
źródło
1
Myślę, że to nie zadziała, jeśli msgChild ma zawsze ten sam status na rodzicu. Na przykład: Chcę komponentu, który otwiera okno modalne. Rodzica nie obchodzi, czy aktualny status jest otwarty czy zamknięty, chce po prostu otworzyć modal w dowolnym momencie. Tak więc, jeśli rodzic to zrobi. MsgChild = true; modalna jest zamknięta, a następnie rodzic ma this.msgChild = true, dziecko nie otrzyma zdarzenie
Jorge Sainz
1
@JorgeSainz: Dlatego właśnie zawijam wartość w tablicy przed przypisaniem jej do elementu danych. Bez zawijania wartości w tablicy zachowuje się dokładnie tak, jak określono. Zatem msgChild = true, msgChild = true - brak zdarzenia. msgChild = [true], msgChild = [true] - zdarzenie!
Jason Stewart
1
Nie widziałem tego. Dzięki za wyjaśnienie
Jorge Sainz
To jest fajne, ale wydaje się trochę hakerskie. Zamierzam go użyć, ponieważ jest czystszy niż użycie ref hackowania składnika i mniej skomplikowany niż rozwiązanie magistrali zdarzeń. Wiem, że vue chce odsprzęgać i zezwalać tylko na zmiany stanu na komponent, ale w razie potrzeby powinien istnieć jakiś wbudowany sposób wywoływania metod dziecka. Być może modyfikator rekwizytu, który po zmianie stanu może automatycznie zresetować go do wartości domyślnej, aby obserwator był gotowy do następnej zmiany stanu. W każdym razie dziękuję za opublikowanie swojego znaleziska.
Craig,
6

Prostym sposobem na wywołanie metod na komponentach potomnych jest wyemitowanie procedury obsługi od dziecka, a następnie wywołanie jej z rodzica.

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
  	setValue(value) {
    	this.value = value;
    }
  },
  created() {
    this.$emit('handler', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
  	setValueHandler(fn) {
    	this.setter = fn
    },
    click() {
    	this.setter(70)
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app">
  <my-component @handler="setValueHandler"></my-component>
  <button @click="click">Click</button>  
</div>

Rodzic śledzi funkcje obsługi dziecka i wywołania, gdy jest to konieczne.

nilobarp
źródło
Podoba mi się, do czego zmierza to rozwiązanie, ale czym dokładnie jest „this.setter” w rodzicu?
Craig,
Jest to odwołanie do funkcji setValue emitowane przez komponent podrzędny jako argument zdarzenia obsługi.
nilobarp
2

Poniższy przykład nie wymaga wyjaśnień. gdzie odwołania i zdarzenia mogą być używane do wywoływania funkcji od i do rodzica i dziecka.

// PARENT
<template>
  <parent>
    <child
      @onChange="childCallBack"
      ref="childRef"
      :data="moduleData"
    />
    <button @click="callChild">Call Method in child</button>
  </parent>
</template>

<script>
export default {
  methods: {
    callChild() {
      this.$refs.childRef.childMethod('Hi from parent');
    },
    childCallBack(message) {
      console.log('message from child', message);
    }
  }
};
</script>

// CHILD
<template>
  <child>
    <button @click="callParent">Call Parent</button>
  </child>
</template>

<script>
export default {
  methods: {
    callParent() {
      this.$emit('onChange', 'hi from child');
    },
    childMethod(message) {
      console.log('message from parent', message);
    }
  }
}
</script>
Mukundhan
źródło
1

Myślę, że powinniśmy wziąć pod uwagę konieczność stosowania przez rodzica metod dziecka. W rzeczywistości rodzice nie muszą zwracać uwagi na metodę dziecka, ale mogą traktować element potomny jako FSA (skończoną maszynę stanową). do kontrolowania stanu składnika potomnego, więc wystarczy obserwować zmianę stanu lub po prostu użyć funkcji obliczeniowej

user10097040
źródło
2
Jeśli mówisz, że „rodzice nigdy nie powinni zajmować się kontrolowaniem dzieci”, są przypadki, w których jest to konieczne. Rozważ element licznika czasu. Rodzic może chcieć zresetować licznik czasu, aby zacząć od nowa. Samo użycie rekwizytów nie wystarczy, ponieważ przejście od time = 60 do time = 60 nie zmieni właściwości. Licznik czasu powinien udostępniać funkcję „reset”, którą rodzic może wywołać w razie potrzeby.
tbm
0

możesz użyć klucza, aby przeładować komponent potomny za pomocą klucza

<component :is="child1" :filter="filter" :key="componentKey"></component>

Jeśli chcesz przeładować komponent z nowym filtrem, kliknij przycisk, przefiltruj komponent potomny

reloadData() {            
   this.filter = ['filter1','filter2']
   this.componentKey += 1;  
},

i użyj filtru, aby wywołać funkcję

Ardian Zack
źródło