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:
- Stan aplikacji
- Rodzic-dziecko
- Dziecko-rodzic
- Rodzeństwo
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 a ref
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.
// 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>
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 :
[S] Czy powinienem stworzyć sklep vuex dla każdej drobnej komunikacji?
Naprawdę błyszczy, gdy:
- radzenie sobie z logiką biznesową,
- komunikowanie się z zapleczem (lub dowolną warstwą trwałości danych, taką jak pamięć lokalna)
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.
- Kontenery specyficzne dla aplikacji
- Komponenty ogólne
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 jakiś sposób events
lub stores
widoczność komponentów rodzeństwa
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
i actions
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ą .
// 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>
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.
// 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>
Nasz kontener przeznaczony dla aplikacji może teraz pełnić rolę pomostu między logiką biznesową a komunikacją prezentacyjną.
// 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>
Ponieważ przechowywanie Vuex działania czynienia z komunikacji backend, nasz pojemnik tutaj nie musi wiedzieć o Axios i backend.
$emit
w połączeniu zv-model
emulacją.sync
. Myślę, że powinieneś pójść drogą Vuex