重用Vue组件中的逻辑

发布于 大漠

特别声明,本文根据@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组件中。但是,我们可以去掉mounteddestroyed的钩子和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  
};

对于mounteddestroyed钩子来说,父节点和子节点都会被保留,并且它们将按照从父节点到子节点的继承顺序被调用。

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不能有任何templatestyle标签,它们只是普通的JavaScript。

编写可重用组件

虽然继承和mixins看起来很容易实现,但它们在某种程度上是隐式的。在前面的示例中,当你使用Scroll功能时,需要知道必要的handleScrollChange方法。

在这种情况下并没有那么糟糕,但是当你扩展或使用多个mixins时,事情可能会变得混乱,特别是开始追踪许多部分的功能时。

另一种重用功能的方法是创建只接收propsemit,这样的方法可能是一种更明确的解决方案,既没有任何合并,也不需要共享上下文。此外,这种解决方案更加通用,因为相同的方法可以应用于任何其他基于组件的技术。

首先,我们必须使用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那么方便。Sneaker Bar Detroit (SBD)