通讯类型
在设计Vue应用程序(或者实际上是任何基于组件的应用程序)时,有不同的通信类型,这取决于我们要处理的问题,并且它们具有自己的通信通道。
业务逻辑:指特定于您的应用及其目标的所有内容。
表示逻辑:用户与之交互或由用户交互产生的任何结果。
这两个问题与以下类型的通信有关:
每种类型都应使用正确的通信渠道。
沟通渠道
渠道是一个宽松的术语,我将用它来指代具体实现以围绕Vue应用程序交换数据。
道具:亲子陈述逻辑
Vue中用于直接父子沟通的最简单的沟通渠道。它应主要用于在层次结构中传递与表示逻辑有关的数据或一组受限制的数据。
参考和方法:演示反模式
如果没有必要使用道具让孩子处理来自父母的事件,则在子组件上设置一个对象ref
并调用其方法就可以了。
不要那样做,这是一种反模式。重新考虑您的组件体系结构和数据流。如果您发现自己想从父级调用子级组件上的方法,则可能是时候提升状态或考虑此处所述或其他答案中所述的其他方式了。
活动:儿童-父母陈述逻辑
$emit
和$on
。直接儿童-父母沟通的最简单的沟通渠道。同样,应将其用于表示逻辑。
活动巴士
大多数答案为事件总线提供了很好的选择,事件总线是可用于远程组件或实际上任何组件的通信通道之一。
当将道具从最远处传递到深层嵌套的子组件时,几乎不需要其他组件之间的传递时,这将变得很有用。谨慎使用谨慎选择的数据。
请注意:随后将自身绑定到事件总线的组件的创建将被绑定一次以上-导致触发多个处理程序和泄漏。我个人从来没有感到过去设计过的所有单页应用程序都需要事件总线。
以下内容演示了一个简单的错误如何导致泄漏,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的商店吗?
当以下情况时,它确实发光:
- 处理您的业务逻辑,
- 与后端(或任何数据持久层,例如本地存储)进行通信
因此,您的组件可以真正专注于它们的本意,即管理用户界面。
这并不意味着您不能将其用于组件逻辑,但我会将其范围限定为仅具有必要的全局UI状态的命名空间Vuex模块。
为了避免在全局状态下处理所有混乱情况,应将存储分为多个命名空间模块。
组件类型
为了协调所有这些通信并简化可重用性,我们应该将组件视为两种不同的类型。
同样,这并不意味着应该重用通用组件或不能重用特定于应用程序的容器,但是它们具有不同的职责。
应用程式专用容器
这些只是包装其他Vue组件(通用或其他特定于应用程序的容器)的简单Vue组件。这就是Vuex商店通信的地方,此容器应通过其他更简单的方式(例如道具和事件监听器)进行通信。
这些容器甚至可能根本没有本机DOM元素,并让通用组件处理模板和用户交互。
范围莫名其妙events
或stores
能见度兄弟姐妹组件
这是范围界定的地方。大多数组件都不知道商店,并且该组件((大多数情况下)应该使用一个命名空间的商店模块,具有有限的一组,getters
并actions
与提供的Vuex绑定帮助程序一起应用)。
通用组件
这些应该从道具接收数据,对自己的本地数据进行更改,并发出简单的事件。大多数时候,他们根本不知道Vuex商店的存在。
它们也可以称为容器,因为它们的唯一职责是分派到其他UI组件。
兄弟交流
那么,毕竟,我们应该如何在两个同级组件之间进行通信?
通过一个示例更容易理解:说我们有一个输入框,它的数据应该在应用程序(树中不同位置的兄弟姐妹)之间共享,并使用后端持久化。
从最坏的情况开始,我们的组件将混合呈现和业务逻辑。
// 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存储操作处理的是后端通信,因此我们这里的容器不需要了解axios和后端。
$emit
结合v-model
模仿.sync
。我认为您应该采用Vuex方式