Posted at

VueとTypeScriptとMixin

More than 1 year has passed since last update.


はじめに

Vue.jsでコンポーネントを作成していると共通の処理をまとめて書きたいときがよくあります。

そんなときに使われるVue.jsの機能がミックスイン(mixin)です。


ミックスイン (mixin) は、Vue コンポーネントに再利用可能で柔軟性のある機能を持たせるための方法です。ミックスインオブジェクトは任意のコンポーネントオプションを含むことができます。コンポーネントがミックスインを使用するとき、ミックスインの全てのオプションはコンポーネント自身のオプションに”混ぜられ”ます。


ミックスイン - Vue.js公式

こんな便利なミックスインですが、定番のvue-class-componentと同じようにクラスベースでの実装例が見当たらなく模索していて、ある程度目処がついたので紹介します。


vue-class-component

(本題ではないので簡単に:expressionless:)

TypeScriptでVueのコンポーネントを作成する際にはよくvue-class-componentがよく使われます。

vue-class-componentを用いることで、使い慣れたクラスベースにコンポーネントを書くことが出来ます。

import Vue from 'vue'

import Component from 'vue-class-component'

// @Component デコレータはクラスが Vue コンポーネントであることを示します
@Component({
// ここではすべてのコンポーネントオプションが許可されています
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
// 初期データはインスタンスプロパティとして宣言できます
message: string = 'Hello!'

// コンポーネントメソッドはインスタンスメソッドとして宣言できます
onClick (): void {
window.alert(this.message)
}
}

クラススタイル Vue コンポーネント - Vue.js公式


vue-mixin-decorator

(こっから本題:wink:)

クラスベースでミックスインするために使うのが、vue-mixin-decoratorというプラグインです。

これは名前の通りミックスインを定義するためのデコレータになります。

使い方は至って単純で、@Componentと同じように使うことが出来ます。

仕組みを知ると当たり前の話なのですが、イメージを持ってもらうために使い方を先に説明します。


1. ミックスインが1つのとき

コンポーネントに"ミックス"したいミックスインが1つの場合は非常に簡単です。

@Mixinでミックスインのクラスを作成した後にMixinsメソッドの返りをextendsします。

import Vue from "vue"

import { Component, Mixin, Mixins } from "vue-mixin-decorator"

@Mixin
class HogeMixin extends Vue {
hogeMessage = "hogehoge"

created() {
console.log("HogeMixin created()")
}

hogeMethod(message: string) {
console.log("hogeMethod() called.", message)
}
}

@Component
class MyComponent extends Mixins<HogeMixin>(HogeMixin) {
created() {
this.hogeMethod(this.hogeMessage)
}
}

Mixinsはクラスではなくメソッドだよ:stuck_out_tongue_closed_eyes:


2. 複数のミックスインのとき

複数の場合は少し工夫が必要です。

ミックスインをそれぞれ定義した後にMixinsの型となるinterfaceを定義してそれをextendsする形になります。

import Vue from "vue"

import { Component, Mixin, Mixins } from "vue-mixin-decorator"

@Mixin
class HogeMixin extends Vue {
hogeMessage = "hogehoge"

created() {
console.log("HogeMixin created()")
}

hogeMethod(message: string) {
console.log("hogeMethod() called.", message)
}
}

@Mixin
class FugaMixin extends Vue {
fugaMessage = "fugafuga"

created() {
console.log("FugaMixin created()")
}

fugaMethod(message: string) {
console.log("fugaMethod() called.", message)
}
}

interface MyConponentMixins extends HogeMixin, FugaMixin {}

@Component
class MyComponent extends Mixins<MyConponentMixins>(HogeMixin, FugaMixin) {
created() {
this.hogeMethod(this.hogeMessage)
this.fugaMethod(this.fugaMessage)
}
}


3. ミックスイン作成時に引数がほしいとき

上2つまではGitHubのREADMEに載っていたので迷うことはありませんでしたが、このパターンで結構はまりました。

汎用的なミックスインでは作成時の引数に応じて動作が変わってほしいこともあります。

たとえば、動きは同じだが引数に応じて受け付けられるpropsが変化するミックスインとか。(プラグインとか作る時には結構ある気がする)

こんなときには、まずは引数が必要なミックスインを通常のクラスとして定義します。

そのあとにミックスインを使うコンポーネント側で上記のクラスをextendsしたミックスインをつくり、

そのミックスインのコンストラクタで親に引数を渡してあげます。

import Vue from "vue"

import { Component, Mixin, Mixins } from "vue-mixin-decorator"

class HogeMixin extends Vue {
constructor(protected hogeMessage: string) { }

// そのままでは呼ばれない
// 子の`created`で明示的に呼んでもらう必要がある
created() {
console.log("HogeMixin created().")
}

hogeMethod() {
console.log(`hogeMethod() called. message=${this.hogeMessage}`)
}
}

@Mixin
class MyComponentHogeMixin extends HogeMixin {
constructor() {
super("Message in MyComponentMixin constructor.")
}
created() {
super.created()
console.log(`MyComponentHogeMixin created. message=${this.hogeMessage}`)
}
}

@Component
class MyComponent extends Mixins<MyComponentHogeMixin>(MyComponentHogeMixin) {
created() {
this.hogeMethod()
}
}

注意点としてはHogeMixinはただのクラスなので、createdmountedなどのライフサイクルフックは自動的にはコールされません。

なので、ミックスインとして定義する側(今回だとMyConponentHogeMixin)でsuper.created()などを明示的に呼ぶ必要があります。

使い方だけで長くなってきたので、vue-mixin-decoratorの解説は別の記事にします:sunglasses:

(いつとは言っていない)