特别声明,本文根据@Alex Jover Morales的《Reusing Logic in Vue Components》一文所整理。
当你开始使用Vue创建应用程序时,你可以开始先创建组件,来构建应用程序的不同部分。你应该可以感受到Vue和Web组件结构体系的良好开发体验。随着项目的进行,你开始以某种方式构造应用程序组件,可能是按页面和组件。
但随着项目的不断发展,你开始要在多个组件之间执行重复的逻辑。我们常常说不要做重复的事情(DRY)和让一切保持它的简单。这两个原则利于我们编写和维护应用程序。
也许你已经知道一些有助于遵循这些原则的模式、库和技术。Vuex将帮助你从组件中提取状态逻辑,Vue路由器将对路由逻辑做同样的工作,但是组件呢?
我们经常遇到这样的情况,需要重用属于组件的一些UI功能。例如,除了被锚定和定位到元素之外,弹出窗(Popover)和提示框(Tooltip)都可以在某个事件触发时共享打开和关闭的功能。
在这篇文章中,我将采用一个Colorful.vue
组件的示例,该组件监听滚动事件并根据滚动位置更改颜色:如果它高于窗口高度,则为蓝色,否则为红色。这是通过使用:style
绑定color
本地状态变量来实现:
<!-- Colorful.vue -->
<template>
<div :style="{ background: color }"></div>
</template>
<script>
export default {
data: () => ({
color: 'red'
}),
methods: {
handleScrollChange(status) {
this.color = status === 'in' ? 'red' : 'blue'
},
handleScroll() {
if (window.scrollY > window.innerHeight) {
this.handleScrollChange('out')
} else {
this.handleScrollChange('in')
}
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
}
}
</script>
添加一点样式,方便我们看效果。这个时候你在页面中滚动鼠标时,能看到这样的效果:
你可能没有在组件中看到任何错误。它在mounted
钩子中注册了scroll
事件,并在destroyed
钩子中删除scroll
事件。它们调用一个handleScroll
方法来检测滚动条位置,并调用handleScrollChange
方法来改变颜色,具体是什么颜色取决于status
参数。
现在的滚动功能是在Colorful
组件中。但是,我们可以去掉mounted
和destroyed
的钩子和handleScroll
方法,以便在其他组件中重用它们。
我们来看看不同的方法。
组件继承
首先让我们将与滚动相关的行为移动到它自己的组件Scroll.js
中:
export default {
methods: {
handleScroll() {
if (window.scrollY > window.innerHeight) {
this.handleScrollChange('out')
} else {
this.handleScrollChange('in')
}
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
}
}
正如你所见,这个滚动组件需要有一个handleScrollChange
,我们在子组件中实现它,这意味着这个组件必须被扩展并且不能单独工作。
因为它只包含JavaScript,所以我们可以将它写在.js
文件中,但如果需要,它也可以是.vue
文件。请记住,只会继承.vue
文件的JavaScript部分。
然后,在Colorful
组件中删除我们移出的组件行为,并使用extends
组件选项导入Scroll
文件:
<!-- Colorful.vue -->
<template>
<div :style="{ background: color }"></div>
</template>
<script>
import Scroll from './Scroll'
export default {
extends: Scroll,
data: () => ({
color: 'red'
}),
methods: {
handleScrollChange(status) {
this.color = status === 'in' ? 'red' : 'blue'
}
}
}
</script>
请注意,组件扩展不是类继承。在本例中,Vue合并父组件和子组件选项,从而创建一个新的混合对象。
例如,在前面的示例中,我们最终会得到包含下面API的一个组件:
{
data: () => ({
color: "red"
}),
methods: {
handleScrollChange(status),
handleScroll,
},
mounted,
destroyed
};
对于mounted
和destroyed
钩子来说,父节点和子节点都会被保留,并且它们将按照从父节点到子节点的继承顺序被调用。
Mixins
与组件继承类似,我们可以使用mixins
来共享组件逻辑。但是在前面的示例中,我们只能从一个组件继承,而使用mixins
,我们可以组合它们的多个功能。
事实上,我们不需要从上一个示例中的Scroll.js
文件中更改任何内容。在Colorful
组件中只需使用mixins
选项来替代extends
选项就足够了。请记住,mixins
需要一个数组:
<!-- Colorful.vue -->
<script>
import Scroll from './Scroll'
export default {
mixins: [Scroll],
data: () => ({
color: 'red'
}),
methods: {
handleScrollChange(status) {
this.color = status === 'in' ? 'red' : 'blue'
}
}
}
</script>
与组件继承的主要区别在于顺序不同:在组件继承中,子组件中的钩子在父组件之前执行,而mixins
的钩子在组件使用它之前执行它们的钩子。
另外,mixins
不能有任何template
和style
标签,它们只是普通的JavaScript。
编写可重用组件
虽然继承和mixins
看起来很容易实现,但它们在某种程度上是隐式的。在前面的示例中,当你使用Scroll
功能时,需要知道必要的handleScrollChange
方法。
在这种情况下并没有那么糟糕,但是当你扩展或使用多个mixins
时,事情可能会变得混乱,特别是开始追踪许多部分的功能时。
另一种重用功能的方法是创建只接收props
和emit
,这样的方法可能是一种更明确的解决方案,既没有任何合并,也不需要共享上下文。此外,这种解决方案更加通用,因为相同的方法可以应用于任何其他基于组件的技术。
首先,我们必须使用Scroll
的.vue
组件:
<!-- Scroll.vue -->
<template>
<div></div>
</template>
<script>
export default {
methods: {
handleScroll() {
if (window.scrollY > window.innerHeight) {
this.$emit('scrollChange', 'out')
} else {
this.$emit('scrollChange', 'in')
}
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
}
}
</script>
在本例中,我们不是调用handleScrollChange
方法,而是发出一个scrollChange
事件,父组件可以使用这个事件来完成它的工作。
然后,在Colorful.vue
中,我们必须奖其作为组件导入并处理scrollChange
事件:
<!-- Colorful.vue -->
<template>
<scroll @scrollChange="handleScrollChange" :style="{ background:color }"></scroll>
</template>
<script>
import Scroll from './Scroll'
export default {
components: {
Scroll
},
data: () => ({
color: 'red'
}),
methods: {
handleScrollChange(status) {
this.color = status === 'in' ? 'red' : 'blue'
}
}
}
</script>
我们已经通过scroll
替换了div
标签,但是Colorful
组件逻辑保持不变。
总结
我们已经看到三种方法来提取滚动功能并重用它。根据你自己的情况,你将选择其中的任何一种方法。
组件继承和mixins
为我们提供了一种分离组件逻辑的一部分并将它们合在一起的方法,这种方法在某些情况下适用,前提条件是它不会使用你的项目管理变得太混乱。特别是mixins
,它更强大,因为它允许将多个mixins
组合在一起。
使用组件组合是一种更清晰,更明确的解决方案。同样的技术也可以用于使用相同技术的其他框架。但在某些情况下,它可能不如mixins
那么方便。