Tipi di comunicazione
Quando si progetta un'applicazione Vue (o in effetti, qualsiasi applicazione basata su componenti), ci sono diversi tipi di comunicazione che dipendono da quali problemi abbiamo a che fare e hanno i propri canali di comunicazione.
Logica aziendale: si riferisce a tutto ciò che è specifico della tua app e del suo obiettivo.
Logica di presentazione: tutto ciò con cui l'utente interagisce o che risulta dall'interazione dell'utente.
Queste due preoccupazioni sono legate a questi tipi di comunicazione:
- Stato dell'applicazione
- Genitore-figlio
- Bambino-genitore
- fratelli
Ogni tipo dovrebbe utilizzare il giusto canale di comunicazione.
Canali di comunicazione
Un canale è un termine generico che userò per fare riferimento a implementazioni concrete per lo scambio di dati su un'app Vue.
Props: logica di presentazione genitore-figlio
Il canale di comunicazione più semplice in Vue per genitori-figli diretti comunicazione . Dovrebbe essere utilizzato principalmente per passare i dati relativi alla logica di presentazione o un insieme limitato di dati lungo la gerarchia.
Rif. E metodi: presentazione anti-pattern
Quando non ha senso usare un oggetto di scena per consentire a un bambino di gestire un evento da un genitore, impostare un ref
sul componente figlio e chiamare i suoi metodi va bene.
Non farlo, è un anti-pattern. Ripensa l'architettura dei componenti e il flusso di dati. Se ti ritrovi a voler chiamare un metodo su un componente figlio da un genitore, è probabilmente il momento di sollevare lo stato o considerare gli altri modi descritti qui o nelle altre risposte.
Eventi: logica di presentazione figlio-genitore
$emit
e $on
. Il canale di comunicazione più semplice per la comunicazione diretta bambino-genitore. Anche in questo caso, dovrebbe essere utilizzato per la logica di presentazione.
Bus per eventi
La maggior parte delle risposte fornisce buone alternative per il bus di eventi, che è uno dei canali di comunicazione disponibili per componenti distanti o qualsiasi altra cosa.
Questo può diventare utile quando si passano oggetti di scena dappertutto da componenti figli molto più in alto fino a componenti figli profondamente annidati, con quasi nessun altro componente che ne ha bisogno nel mezzo. Utilizzare con parsimonia per dati accuratamente selezionati.
Stai attento: creazione successiva di componenti che si legano al bus degli eventi verrà associata più di una volta, causando l'attivazione di più gestori e perdite. Personalmente non ho mai sentito il bisogno di un bus di eventi in tutte le app a pagina singola che ho progettato in passato.
Quanto segue dimostra come un semplice errore porti a una perdita in cui il Item
componente si attiva ancora anche se rimosso dal 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>
Ricordarsi di rimuovere i listener destroyed
nell'hook del ciclo di vita.
Negozio centralizzato (logica aziendale)
Vuex è la strada da percorrere con Vue per la gestione dello stato . Offre molto di più che semplici eventi ed è pronto per l'applicazione su vasta scala.
E ora chiedi :
[S] dovrei creare il negozio di vuex per ogni comunicazione minore?
Brilla davvero quando:
- affrontare la tua logica aziendale,
- comunicare con un backend (o qualsiasi livello di persistenza dei dati, come l'archiviazione locale)
Quindi i tuoi componenti possono davvero concentrarsi sulle cose che dovrebbero essere, gestendo le interfacce utente.
Ciò non significa che non sia possibile utilizzarlo per la logica dei componenti, ma definirei tale logica in un modulo Vuex con spazio dei nomi con solo lo stato dell'interfaccia utente globale necessario.
Per evitare di dover gestire un grosso casino di tutto in uno stato globale, il negozio dovrebbe essere separato in più moduli con spazio dei nomi.
Tipi di componenti
Per orchestrare tutte queste comunicazioni e facilitare il riutilizzo, dovremmo pensare ai componenti come a due tipi diversi.
- Contenitori specifici dell'app
- Componenti generici
Ancora una volta, ciò non significa che un componente generico debba essere riutilizzato o che un contenitore specifico dell'app non possa essere riutilizzato, ma hanno responsabilità diverse.
Contenitori specifici dell'app
Questi sono solo semplici componenti Vue che avvolgono altri componenti Vue (contenitori generici o altri contenitori specifici dell'app). È qui che dovrebbe avvenire la comunicazione del negozio Vuex e questo contenitore dovrebbe comunicare attraverso altri mezzi più semplici come oggetti di scena e ascoltatori di eventi.
Questi contenitori potrebbero anche non avere alcun elemento DOM nativo e lasciare che i componenti generici gestiscano i modelli e le interazioni degli utenti.
ambito in qualche modo events
o stores
visibilità per i componenti dei fratelli
È qui che avviene lo scoping. La maggior parte dei componenti non conosce il negozio e questo componente dovrebbe (principalmente) utilizzare un modulo di archivio con spazio dei nomi con un set limitato di getters
e actions
applicato con gli helper di binding Vuex forniti .
Componenti generici
Questi dovrebbero ricevere i dati dagli oggetti di scena, apportare modifiche ai propri dati locali ed emettere eventi semplici. Il più delle volte, non dovrebbero sapere che esiste un negozio Vuex.
Potrebbero anche essere chiamati contenitori poiché la loro unica responsabilità potrebbe essere l'invio ad altri componenti dell'interfaccia utente.
Comunicazione tra fratelli
Quindi, dopo tutto questo, come dovremmo comunicare tra due componenti fratelli?
È più facile da capire con un esempio: supponiamo di avere una casella di input e i suoi dati dovrebbero essere condivisi attraverso l'app (fratelli in punti diversi dell'albero) e persistenti con un backend.
Partendo dallo scenario peggiore , il nostro componente mescolerebbe presentazione e logica aziendale .
// 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>
Per separare queste due preoccupazioni, dovremmo racchiudere il nostro componente in un contenitore specifico dell'app e mantenere la logica di presentazione nel nostro componente di input generico.
Il nostro componente di input è ora riutilizzabile e non conosce il backend né i fratelli.
// 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>
Il nostro contenitore specifico per app può ora essere il ponte tra la logica aziendale e la comunicazione di presentazione.
// 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>
Poiché le azioni del negozio Vuex riguardano la comunicazione back-end, il nostro contenitore qui non ha bisogno di conoscere axios e backend.
$emit
combinato conv-model
emulare.sync
. Penso che dovresti seguire la via