Przegląd
W Vue.js 2.x model.sync
zostanie wycofany .
Więc jaki jest właściwy sposób komunikacji między komponentami rodzeństwa w Vue.js 2.x ?
tło
Jak rozumiem Vue 2.x, preferowaną metodą komunikacji z rodzeństwem jest użycie sklepu lub magistrali zdarzeń .
Według Evana (twórcy Vue):
Warto również wspomnieć, że „przekazywanie danych między komponentami” jest generalnie złym pomysłem, ponieważ ostatecznie przepływ danych staje się niemożliwy do śledzenia i bardzo trudny do debugowania.
Jeśli część danych musi być współdzielona przez wiele komponentów, preferuj sklepy globalne lub Vuex .
[ Link do dyskusji ]
I:
.once
i.sync
są przestarzałe. Rekwizyty są teraz zawsze w dół. Aby uzyskać efekty uboczne w zakresie nadrzędnym, składnik musi jawnie jawnieemit
zdarzenie zamiast polegać na niejawnym powiązaniu.
Tak więc Evan sugeruje użycie $emit()
i $on()
.
Obawy
Martwi mnie:
- Każdy
store
ievent
ma globalną widoczność (popraw mnie, jeśli się mylę); - Tworzenie nowego sklepu dla każdej drobnej komunikacji jest zbyt marnotrawne;
To, czego chcę, to pewien zakres events
lub stores
widoczność komponentów rodzeństwa. (A może nie rozumiałem powyższego pomysłu.)
Pytanie
Jaki jest więc prawidłowy sposób komunikacji między komponentami rodzeństwa?
źródło
$emit
w połączeniu zv-model
emulacją.sync
. Myślę, że powinieneś pójść drogą VuexOdpowiedzi:
W Vue 2.0 używam mechanizmu eventHub, jak pokazano w dokumentacji .
Zdefiniuj scentralizowane centrum zdarzeń.
Teraz w swoim komponencie możesz emitować zdarzenia za pomocą
I słuchasz
Aktualizacja Zobacz odpowiedź @alex , która opisuje prostsze rozwiązanie.
źródło
this.$root.$emit()
ithis.$root.$on()
Możesz nawet go skrócić i używać wystąpienia głównego
Vue
jako globalnego centrum zdarzeń:Składnik 1:
Składnik 2:
źródło
Typy komunikacji
Projektując aplikację Vue (a właściwie dowolną aplikację opartą na komponentach), istnieją różne typy komunikacji, które zależą od problemów, z którymi mamy do czynienia i mają własne kanały komunikacji.
Logika biznesowa: odnosi się do wszystkiego, co dotyczy Twojej aplikacji i jej celu.
Logika prezentacji: wszystko, z czym użytkownik wchodzi w interakcje lub co wynika z interakcji użytkownika.
Te dwa problemy są związane z tymi rodzajami komunikacji:
Każdy typ powinien korzystać z odpowiedniego kanału komunikacji.
Kanały komunikacji
Kanał to luźny termin, którego będę używał w odniesieniu do konkretnych implementacji służących do wymiany danych wokół aplikacji Vue.
Rekwizyty: logika prezentacji rodzic-dziecko
Najprostszy kanał komunikacji w Vue dla bezpośredniego rodzica-dziecka komunikacji . Powinien być używany głównie do przekazywania danych związanych z logiką prezentacji lub ograniczonym zestawem danych w dół hierarchii.
Odniesienia i metody: Prezentacja anty-wzorca
Kiedy nie ma sensu używanie właściwości pozwalającej dziecku na obsługę zdarzenia rodzica, ustawienie aref
na komponencie potomnym i wywołanie jego metod jest w porządku.Nie rób tego, to anty-wzór. Przemyśl swoją architekturę komponentów i przepływ danych. Jeśli okaże się, że chcesz wywołać metodę na komponencie potomnym od rodzica, prawdopodobnie nadszedł czas, aby podnieść stan lub rozważyć inne sposoby opisane tutaj lub w innych odpowiedziach.
Zdarzenia: logika prezentacji dziecko-rodzic
$emit
i$on
. Najprostszy kanał komunikacji do bezpośredniej komunikacji dziecko-rodzic. Ponownie powinno być używane do logiki prezentacji.Autobus imprezowy
Większość odpowiedzi podaje dobre alternatywy dla magistrali zdarzeń, która jest jednym z kanałów komunikacyjnych dostępnych dla odległych komponentów, lub czymkolwiek w rzeczywistości.
Może się to przydać przy przekazywaniu rekwizytów w każdym miejscu z daleka w dół do głęboko zagnieżdżonych komponentów potomnych, prawie żadne inne komponenty nie potrzebują ich pomiędzy. Używaj oszczędnie w przypadku starannie wybranych danych.
Uważaj: późniejsze tworzenie komponentów, które są powiązane z magistralą zdarzeń, będzie wiązane więcej niż raz - co prowadzi do wyzwolenia wielu procedur obsługi i wycieków. Osobiście nigdy nie odczuwałem potrzeby korzystania z magistrali zdarzeń we wszystkich aplikacjach z jedną stroną, które zaprojektowałem w przeszłości.
Poniżej pokazano, jak prosty błąd prowadzi do wycieku, w którym
Item
komponent nadal jest wyzwalany, nawet jeśli zostanie usunięty z DOM.Pokaż fragment kodu
Pamiętaj, aby usunąć detektory w
destroyed
haku cyklu życia.Scentralizowany sklep (logika biznesowa)
Vuex to sposób na zarządzanie stanem dzięki Vue . Oferuje znacznie więcej niż tylko wydarzenia i jest gotowy do aplikacji na pełną skalę.
A teraz pytasz :
Naprawdę błyszczy, gdy:
Dzięki temu komponenty mogą naprawdę skupić się na tym, czym mają być, zarządzając interfejsami użytkownika.
Nie oznacza to, że nie możesz go użyć do logiki komponentów, ale chciałbym ograniczyć tę logikę do modułu Vuex z przestrzenią nazw z tylko niezbędnym globalnym stanem interfejsu użytkownika.
Aby uniknąć bałaganu wszystkiego w stanie globalnym, sklep powinien być podzielony na wiele modułów z przestrzenią nazw.
Typy komponentów
Aby zorganizować całą tę komunikację i ułatwić ponowne użycie, powinniśmy traktować komponenty jako dwa różne typy.
Ponownie, nie oznacza to, że należy ponownie użyć komponentu ogólnego lub że nie można ponownie użyć kontenera specyficznego dla aplikacji, ale mają inne obowiązki.
Kontenery specyficzne dla aplikacji
To tylko prosty komponent Vue, który otacza inne komponenty Vue (ogólne lub inne kontenery specyficzne dla aplikacji). To tutaj powinna mieć miejsce komunikacja sklepu Vuex, a ten kontener powinien komunikować się za pomocą innych prostszych środków, takich jak rekwizyty i nasłuchiwanie zdarzeń.
Te kontenery mogą nawet nie mieć w ogóle natywnych elementów DOM i pozwolić komponentom generycznym zajmować się tworzeniem szablonów i interakcjami użytkownika.
W tym miejscu odbywa się określanie zakresu. Większość komponentów nie wie o sklepie, a ten komponent powinien (głównie) używać jednego modułu sklepu z przestrzenią nazw z ograniczonym zestawem
getters
iactions
zastosowanych za pomocą dostarczonych pomocników powiązań Vuex .Komponenty ogólne
Powinny one otrzymywać dane z rekwizytów, wprowadzać zmiany we własnych danych lokalnych i emitować proste zdarzenia. W większości przypadków nie powinni wiedzieć, że sklep Vuex w ogóle istnieje.
Można je również nazwać kontenerami, ponieważ ich wyłączną odpowiedzialnością może być wysyłanie do innych składników interfejsu użytkownika.
Komunikacja z rodzeństwem
Jak więc po tym wszystkim powinniśmy komunikować się między dwoma komponentami rodzeństwa?
Łatwiej to zrozumieć na przykładzie: powiedzmy, że mamy pole wprowadzania, a jego dane powinny być udostępniane w całej aplikacji (rodzeństwo w różnych miejscach w drzewie) i utrwalane na zapleczu.
Zaczynając od najgorszego scenariusza , nasz komponent łączyłby prezentację i logikę biznesową .
Aby oddzielić te dwa problemy, powinniśmy opakować nasz składnik w kontenerze specyficznym dla aplikacji i zachować logikę prezentacji w naszym ogólnym składniku wejściowym.
Nasz komponent wejściowy jest teraz wielokrotnego użytku i nie wie o zapleczu ani rodzeństwie.
Nasz kontener przeznaczony dla aplikacji może teraz pełnić rolę pomostu między logiką biznesową a komunikacją prezentacyjną.
Ponieważ przechowywanie Vuex działania czynienia z komunikacji backend, nasz pojemnik tutaj nie musi wiedzieć o Axios i backend.
źródło
OK, możemy komunikować się między rodzeństwem za pośrednictwem rodziców za pomocą
v-on
wydarzeń.Załóżmy, że chcemy aktualizować
Details
komponent po kliknięciu jakiegoś elementuList
.w
Parent
:Szablon:
Tutaj:
v-on:select-item
jest to zdarzenie, które zostanie wywołane wList
komponencie (patrz poniżej);setSelectedItem
jest toParent
metoda aktualizacjiselectedModel
;JS:
W
List
:Szablon:
JS:
Tutaj:
this.$emit('select-item', item)
wyśle przedmiotselect-item
bezpośrednio do rodzica. A rodzic wyśle go doDetails
widokuźródło
To, co zwykle robię, jeśli chcę „zhakować” normalne wzorce komunikacji w Vue, szczególnie teraz, gdy
.sync
jest przestarzałe, to utworzenie prostego EventEmittera, który obsługuje komunikację między komponentami. Z jednego z moich najnowszych projektów:Za pomocą tego
Transmitter
obiektu możesz następnie wykonać w dowolnym komponencie:Aby utworzyć komponent „odbierający”:
Ponownie, to jest do naprawdę konkretnych zastosowań. Nie opieraj całej aplikacji na tym wzorcu,
Vuex
zamiast tego użyj czegoś podobnego .źródło
vuex
, ale znowu, czy powinienem utworzyć sklep vuex dla każdej drobnej komunikacji?vuex
tak, zrób to. Użyj tego.Sposób radzenia sobie z komunikacją między rodzeństwem zależy od sytuacji. Ale najpierw chcę podkreślić, że globalne podejście do magistrali zdarzeń odchodzi w Vue 3 . Zobacz ten dokument RFC . Dlatego zdecydowałem się napisać nową odpowiedź.
Najniższy wspólny wzorzec przodka (lub „LCA”)
W prostych przypadkach zdecydowanie zalecam użycie wzorca Lowest Common Ancestor (znanego również jako „data down, event up”). Ten wzorzec jest łatwy do odczytania, zaimplementowania, przetestowania i debugowania.
W istocie oznacza to, że jeśli dwa komponenty muszą się komunikować, umieść ich wspólny stan w najbliższym komponencie, który ma wspólny przodek. Przekaż dane z komponentu nadrzędnego do komponentu podrzędnego za pomocą rekwizytów i przekaż informacje od dziecka do rodzica, emitując zdarzenie (zobacz przykład na dole odpowiedzi).
W wymyślonym przykładzie, w aplikacji e-mail, jeśli komponent „Do” musiałby współdziałać z komponentem „treść wiadomości”, stan tej interakcji mógłby istnieć w ich rodzicu (może być wywołany komponent
email-form
). Możesz mieć prop wemail-form
wywołaniu,addressee
aby treść wiadomości mogła być automatycznie dołączanaDear {{addressee.name}}
do wiadomości e-mail na podstawie adresu e-mail odbiorcy.LCA staje się uciążliwe, jeśli komunikacja musi odbywać się na duże odległości z wieloma komponentami pośredników. Często odsyłam kolegów do tego wspaniałego wpisu na blogu . (Zignoruj fakt, że jego przykłady wykorzystują Ember; jego pomysły mają zastosowanie w wielu strukturach interfejsu użytkownika).
Wzorzec kontenera danych (np. Vuex)
W złożonych przypadkach lub sytuacjach, w których komunikacja między rodzicem a dzieckiem wymagałaby zbyt wielu pośredników, użyj Vuex lub równoważnej technologii kontenera danych. W razie potrzeby użyj modułów w przestrzeni nazw .
Na przykład rozsądne może być utworzenie oddzielnej przestrzeni nazw dla złożonej kolekcji składników z wieloma połączeniami, na przykład w pełni funkcjonalnego składnika kalendarza.
Wzorzec publikowania / subskrypcji (magistrala zdarzeń)
Jeśli wzorzec magistrali zdarzeń (lub wzorca „publikuj / subskrybuj”) jest bardziej odpowiedni dla Twoich potrzeb, główny zespół Vue zaleca teraz korzystanie z biblioteki innej firmy, takiej jak rękawica . (Zobacz dokument RFC, o którym mowa w akapicie 1.)
Bonusowe wędrówki i kod
Oto podstawowy przykład rozwiązania Lowest Common Ancestor do komunikacji między rodzeństwem, zilustrowany za pomocą gry Whack-a-mole .
Naiwnym podejściem może być myślenie, „kret 1 powinien powiedzieć kretowi 2, aby pojawił się po uderzeniu go”. Ale Vue odradza tego rodzaju podejście, ponieważ chce, abyśmy myśleli w kategoriach struktur drzewiastych .
To prawdopodobnie bardzo dobra rzecz. Nietrywialna aplikacja, w której węzły komunikują się bezpośrednio ze sobą między drzewami DOM, byłaby bardzo trudna do debugowania bez jakiegoś systemu księgowego (jak zapewnia Vuex). Co więcej, komponenty wykorzystujące „dane w dół, zdarzenia w górę” mają zwykle niskie sprzężenie i dużą możliwość ponownego użycia - obie bardzo pożądane cechy, które pomagają skalować duże aplikacje.
W tym przykładzie, gdy kret zostanie uderzony, emituje zdarzenie. Komponent menedżera gier decyduje o nowym stanie aplikacji, dzięki czemu kret rodzeństwo wie, co zrobić niejawnie po ponownym renderowaniu Vue. To dość trywialny przykład „najniższego wspólnego przodka”.
źródło