Vue2.0学习笔记:Vue中的Mixins

在项目开发的时候,常会碰到这样的一种现象:有两个组件非常相似,比如较为熟悉的ModalTooltipPopover,它们都具有同样的基本函数,而且它们之前也有足够的不同。很多时候,就让人很难做出选择:是把它们拆会成多个不同的组件呢?还是只使用一个组件,创建足够的属性来改变不同的情况。

这些解决方案都不够完美。如果拆会成多个组件,就不得不冒着如果功能变动你要在多个文件中更新它的风险。另一方面,太多的属笥会很快变得混乱,难维护,甚至对于组件开发者自已面言,也是件难事。

在Vue中,对于这样的场景,官方提供了一种叫混入(mixins)的特性。使用mixins允许你封装一块在应用的其他组件中都可以使用的函数。如果被正确的使用,他们不会改变函数作用域外部的任何东西,所以多次执行,只要是同样的输入,总是能得到一样的值。

既然Vue的mixins这么优秀,那应该怎么使用呢?今天我们的目的就是学习如何在Vue的项目中使用mixins

什么是Mixins

Vue的官方文档是这样描述mixins的:

mixins是一种分发Vue组件中可复用功能的一种灵活方式。

mixins是一个JavaScript对象,可以包含组件中的任意选项,比如Vue实例中生命周期的各个钩子函数,也可以是datacomponentsmethodsdirectives等。在Vue中,mixins为我们提供了在Vue组件中共用功能的方法。使用方式很简单,将共用的功能以对象的方式传入mixins选项中。当组件使用mixins对象时,所有mixins对象的选项都将被混入该组件本身的选项。

如何创建Mixins

在Vue中,创建mixins方法很简单。简单一点的,在Vue组件中,我们将mixins对象指定为:

const myMixin = {
    created () {
        console.log(`来自Mixins中的消息`)
    }
}

let app = new Vue({
    el: '#app',
    mixins: [myMixin]
})

上面看到的方式是在组件中声明一个mixins对象。如果组件多起来,这样的方式,感觉还是没有解决我们文章提到的需求一样。更好的做法,我们可以像下面这样做。比如在项目的src下面创建一个存放mixins的目录,比如一个叫mixin目录。然后这个目录中创建你想要的.js文件,比如这里创建了一个叫mixins.js:

为了方便演示,把上面创建的mixins相关的代码移入mixins.js文件中:

// src/mixins/mixins.js
export const myMixin = {
    created() {
        console.log(`来自Mixins中的消息`);
    }
};

在需要使用该mixins的组件中先引入mixins.js文件,接着在组件中的mixins选择中调用在mixins.js创建的mixins,比如:

// HelloWorld.vue
<script>
import { myMixin, myMixin2 } from "../mixins/mixins.js";

export default {
    name: "HelloWorld",
    data() {
        return {
            msg: "From Vue Component"
        };
    },
    mixins: [myMixin]
};
</script>

这种方式和前面使用mixins方式最终得到的结果是一样的。

使用mixins,在mixins中指定的任何选项都会立即与Vue组件的选项合并。可以在组件的mixins数组中指定多个mixin。一旦指定了,所有的mixin选项都会被导入到组件中。比如:

// mixins.js
export const mixObj = {
    created() {
        this.hello()
    },
    methods: {
        hello() {
            console.log(`Hello from Mixin`)
        }
    }
}
export const minObjAnother = {
    created () {
        this.prompt()
    },
    methods: {
        prompt() {
            console.log(`Prompt from Mixin Another`)
        },
        hello() {
            console.log(`Hello from Mixin Another`)
        }
    }
}

如果在组件中要同时调用mixObjminObjAnother两个mixins时,需要在组件的mixins选项的值添加这两个mixins,比如:

<!-- HelloWorld.vue -->
<script>
import { mixObj, minObjAnother } from "../mixins/mixins.js";

export default {
    name: "HelloWorld",
    mixins: [mixObj, minObjAnother],
};
</script>

此时在console中输出的结果如下:

从输出的结果中我们可以看出,mixObjAnother中的hello()方法取替了mixObj中的hello()方法。

是不是感到疑惑。如果是,请继续往下阅读。

合并选项和冲突

当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。

什么是“恰当的方式”?对于我这样的初学者而言,有点难于理解。上例中我们看到了,每个mixins对象中都具有生命周期的钩子(Vue生命周期)和方法(甚至其他的),而且这些东西都是可用的。另外在Vue中的组件,同样也有这些生命周期的钩子函数和方法。所以当在组件上注册重复的过程时,顺序显得尤其重要。

默认是mixins上会首先被注册,组件上的接着注册!

这样的做主要是因为必要时可以重写。

组件拥有最终发言权: 当有一个冲突并且这个组件不得不决定哪个胜出的时候,这真的变得很重要,否则,所有东西都被放在一个数组中执行,mixins中的先执行,组件中的接着执行。

在Vue中mixins常见的合并主要有三种情形:数据data、生命周期中的钩子函数和值为对象的选项。接下来,咱们分别来看看这三种情形:

数据合并

数据对象在内部分进行浅合并(一层属性深度),在和组件的数据发生冲突时,以组件数据优先

// mixins.js
export const mixObj = {
    data() {
        return {
            msg: "Message from Mixin Object"
        };
    }
};
export const minObjAnother = {
    data() {
        return {
            msg: "Message from Mixin Object Another",
            name: "Foo"
        };
    }
};

<!-- HelloWorld.vue -->
<script>
    import { mixObj, minObjAnother } from "../mixins/mixins.js";

    export default {
        name: "HelloWorld",
        data() {
            return {
                msg: "Message from Vue Component",
                name: 'Bar',
                age: 20
            };
        },
        mixins: [mixObj, minObjAnother],
        created () {
            console.log(this.$data)
        }
    };
</script>

最终的结果是,HelloWorld组件中的msgname取替了mixins中的,另外age字段被合并进行。

钩子函数合并

当组件使用的mixins对象有相同的选项时,比如钩子函数,就会全部被合并到一个数组中,因此都会被执行,并且mixins`对象中的钩子会先被执行。

const myMixin = {
    created () {
        console.log(`来自Mixins中的消息`)
    }
}

new Vue({
    el: '#app',
    mixins: [myMixin],
    created () {
        console.log(`来自组件中的消息`)
    }
})

值为对象选项合并

mixins对象和组件中的选项的值为对象时,比如methodscomponentsdirectives,将被混合为同一个对象。当两个对象键名冲突时,组件选项优先

const myMixin = {
    data () {
        return {
            msg: 'From Vue Mixins'
        }
    },
    created() {
        console.log(`来自Mixins的消息`)
    },
    methods: {
        message () {
            console.log(this.msg)
        }
    }
};
export default {
    name: "HelloWorld",
    data() {
        return {
            msg: "From Vue Component"
        };
    },
    mixins: [myMixin],
    created() {
        console.log(`来自组件的消息`)
    },
    methods: {
        message () {
            console.log(this.msg)
        }
    }
};

全局Mixins

当我们使用全局混合时,我们不是指能够在每个组件上访问它们,就像是过滤器一样。我们能够通过mixins:[myMixins]访问组件上的混合对象。

全局混合被注册到了每个单一组件上。因此,它们的使用场景极其有限并且要非常的小心。一个我能想到的用途就是它像一个插件,你需要赋予它访问所有东西的权限。但即使在这种情况下,我也对你正在做的保持警惕,尤其是你在应用中扩展的函数,可能对你来说是不可知的。

为了创建一个全局实例,我们可以把它放在 Vue 实例之上。在一个典型的 Vue-cli 初始化的项目中,它可能在你的main.js文件中。

Vue.mixin({
    mounted() {
        console.log('hello from mixin!')
    }
})

new Vue({
    ...
})

谨慎使用全局混入对象,因为会影响到每个单独创建的 Vue 实例 (包括第三方模板)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。也可以将其用作 Plugins 以避免产生重复应用。

使用Mixins实例

经过上面的介绍,我们对Vue中的mixins应该有了一定的了解。接下来通过简单的实例来向大家阐述在项目中怎么使用上面介绍的mixins

先来看一个简单的示例,也就是文章中提到的ModalTooltip组件。虽然这两个组件是不同的组件,但实际上它们的作用是类似的:切换一个状态布尔值。换句话说,这两个组件表面上看起来不一样,用法不一样,但逻辑是一样的。

// 模态框
const Modal = {
    template: '#modal',
    data() {
        return {
            isShowing: false
        }
    },
    methods: {
        toggleShow() {
            this.isShowing = !this.isShowing;
        }
    },
    components: {
        appChild: Child
    }
}

// 提示框
const Tooltip = {
    template: '#tooltip',
    data() {
        return {
            isShowing: false
        }
    },
    methods: {
        toggleShow() {
            this.isShowing = !this.isShowing;
        }
    },
    components: {
        appChild: Child
    }
}

我们可以提取出这个逻辑并创建可以被重用的项:

// toggle.js
export const toggle = {
    data() {
        return {
            isShowing: false
        };
    },
    methods: {
        toggleShow() {
            this.isShowing = !this.isShowing;
        }
    }
};

<!-- Modal.vue -->
<template>
    <div>
        <h3>Let's trigger this here modal!</h3>
        <button @click="toggleShow">
            <span v-if="isShowing">Hide child</span>
            <span v-else>Show child</span>
        </button>
        <Child v-if="isShowing" class="modal">
            <button @click="toggleShow">Close</button>
        </Child>
    </div>
</template>

<script>
    import Child from "./Child";
    import { toggle } from "../mixins/toggle.js";

    export default {
        name: "Modal",
        mixins: [toggle],
        components: {
            Child
        }
    };
</script>

<!-- ToolTip.vue -->
<template>
    <div class="tooltip-demo">
        <h3 @click="toggleShow">
            <span v-if="isShowing">Click Me Again</span>
        <span v-else>Click Me Please</span>
        </h3>
        <Child v-if="isShowing" class="tooltip">
            <p>I'm a tooltip this time</p>
        </Child>
    </div>
</template>

<script>
    import { toggle } from '../mixins/toggle.js'
    import Child from './Child'

    export default {
        name: 'ToolTip',
        mixins: [toggle],
        components: {
            Child
        }
    }
</script>

总结

混合对于封装一小段想要复用的代码来讲是有用的。对你来说它们当然不是唯一可行的选择:高阶组件,例如,允许组合相似函数,这只是实现的一种方式。我喜欢混合,因为我不需要传递状态,但是这种模式当然也可能会被滥用,所以,仔细思考哪种选择对你的应用最有意义。

参考阅读

返回顶部