Типы общения
При разработке приложения Vue (или, фактически, любого приложения на основе компонентов) существуют разные типы связи, которые зависят от того, с какими проблемами мы имеем дело, и у них есть свои собственные каналы связи.
Бизнес-логика: относится ко всему, что связано с вашим приложением и его целью.
Логика представления: все, с чем взаимодействует пользователь или что является результатом взаимодействия с пользователем.
Эти две проблемы связаны с этими типами общения:
- Состояние приложения
- Родитель-ребенок
- Ребенок-родитель
- Братья и сестры
Каждый тип должен использовать правильный канал связи.
Каналы связи
Канал - это общий термин, который я буду использовать для обозначения конкретных реализаций для обмена данными в приложении Vue.
Реквизиты: логика представления родитель-ребенок
Самый простой канал связи в Vue для прямого общения родителей и детей . В основном его следует использовать для передачи данных, относящихся к логике представления или ограниченному набору данных вниз по иерархии.
Ссылки и методы: Презентационный антипаттерн
Когда нет смысла использовать опору, позволяющую дочернему элементу обрабатывать событие от родителя, настройка ref
дочернего компонента и вызов его методов - это нормально.
Не делай этого, это антипаттерн. Переосмыслите архитектуру компонентов и поток данных. Если вы обнаружите, что хотите вызвать метод дочернего компонента из родительского, вероятно, пора поднять состояние или рассмотреть другие способы, описанные здесь или в других ответах.
События: логика представления дочернего и родительского элементов
$emit
и $on
. Самый простой канал связи для прямого общения Родитель-Ребенок. Опять же, следует использовать для логики представления.
Автобус событий
Большинство ответов дают хорошие альтернативы для шины событий, которая является одним из каналов связи, доступных для удаленных компонентов, или чего-то еще.
Это может стать полезным при передаче props повсюду, начиная с самых глубоко вложенных дочерних компонентов, и почти никаким другим компонентам они не нужны. Используйте осторожно для тщательно отобранных данных.
Будьте осторожны: последующее создание компонентов, которые привязываются к шине событий, будет связано более одного раза, что приведет к срабатыванию нескольких обработчиков и утечкам. Я лично никогда не чувствовал необходимости в шине событий во всех одностраничных приложениях, которые я проектировал в прошлом.
Ниже показано, как простая ошибка приводит к утечке, когда Item
компонент все еще запускается, даже если он удален из 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>
Не забудьте удалить слушателей в destroyed
ловушке жизненного цикла.
Централизованный магазин (Бизнес-логика)
Vuex - это способ использовать Vue для управления состоянием . Он предлагает гораздо больше, чем просто события, и готов к полномасштабному применению.
А теперь вы спрашиваете :
[S] Могу ли я создать магазин vuex для каждого второстепенного сообщения?
Он действительно сияет, когда:
- работа с вашей бизнес-логикой,
- связь с серверной частью (или любым уровнем сохранения данных, например локальным хранилищем)
Таким образом, ваши компоненты могут действительно сосредоточиться на том, чем они должны быть, на управлении пользовательскими интерфейсами.
Это не означает, что вы не можете использовать его для логики компонентов, но я бы применил эту логику к модулю Vuex с пространством имен только с необходимым глобальным состоянием пользовательского интерфейса.
Чтобы избежать большого беспорядка всего в глобальном состоянии, хранилище следует разделить на несколько модулей с именами.
Типы компонентов
Чтобы организовать все эти коммуникации и упростить повторное использование, мы должны рассматривать компоненты как два разных типа.
- Контейнеры для конкретных приложений
- Общие компоненты
Опять же, это не означает, что общий компонент следует повторно использовать или что конкретный контейнер приложения нельзя повторно использовать, но у них разные обязанности.
Контейнеры для конкретных приложений
Это простой компонент Vue, который обертывает другие компоненты Vue (общие или другие контейнеры, специфичные для приложения). Здесь должно происходить взаимодействие хранилища Vuex, и этот контейнер должен взаимодействовать с помощью других более простых средств, таких как реквизиты и прослушиватели событий.
Эти контейнеры могут вообще не иметь собственных элементов DOM и позволять универсальным компонентам обрабатывать шаблоны и взаимодействие с пользователем.
Объем каким - то образом events
или stores
видимость для сибсов компонентов
Вот где происходит определение объема работ. Большинство компонентов не знают о хранилище, и этот компонент должен (в основном) использовать один модуль хранилища с пространством имен с ограниченным набором getters
и actions
применяться с предоставленными помощниками привязки Vuex .
Общие компоненты
Они должны получать свои данные от props, вносить изменения в свои локальные данные и генерировать простые события. В большинстве случаев они не должны знать, что магазин Vuex вообще существует.
Их также можно назвать контейнерами, поскольку их единственная ответственность может заключаться в отправке другим компонентам пользовательского интерфейса.
Братское общение
Итак, после всего этого, как мы должны общаться между двумя родственными компонентами?
Это легче понять на примере: допустим, у нас есть поле ввода, и его данные должны совместно использоваться в приложении (братья и сестры в разных местах в дереве) и сохраняться с помощью серверной части.
Начиная с наихудшего сценария , наш компонент будет смешивать презентацию и бизнес- логику.
// 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 действия иметь дело с серверной связи, наш контейнер здесь не нужно знать о Вардар и внутреннем интерфейсе.
$emit
в сочетании сv-model
подражать.sync
. Я думаю, вам стоит пойти по пути