mixin って何だろう?

最近「mixin」という単語を見かけることがありました。
よくわかっていなかったので、メモがてらまとめました。

目次

参考

mixin ってなんだ?

「ミキシン」なのか「ミックスイン」なのかがわかっていなかったが、読み方は「ミックスイン」が正しい。

Wikipedia によると、以下の通り。

mixin とはオブジェクト指向プログラミング言語において、サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラスである。言語によっては、その言語でクラスや継承と呼ぶものとは別のシステムとして mixin がある場合もある

ここまででは、「C++のインターフェースみたいなもの?」という理解をしました。

ここから JavaScript で mixin を見ていきます。
MDN web docs - クラスを参照します。
以下引用です。

抽象的なサブクラスやミックスインはクラスのためのテンプレートです。 ECMAScript のクラスは 1 つだけスーパークラスを持つことができます。そのため、多重継承はできません。機能はスーパークラスから提供されます。

ECMAScript では、スーパークラスをインプットとして、そしてスーパークラスを継承した派生クラスをアウトプットとする関数を mix-in で実装できます:

加えて、以下の 2 サイトを参照して、以下のテストコードを用意してみました。

テストコード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const runSkilMixin = (Base) => {
class extendedClass extends Base {
execSkil() {
this.run();
}
run() {
console.log(`${this.name} is run.`);
}
}
return extendedClass;
};
const swimSkilMixin = (Base) => {
class extendedClass extends Base {
execSkil() {
this.swim();
}
swim() {
console.log(`${this.name} is swim.`);
}
}
return extendedClass;
};

// 拡張の基底になるクラス
class Animal {
constructor(name) {
this.name = name;
}
execSkil() {
console.log("Not Setup");
}
}

// 基底のAnimalをそれぞれの拡張の関数を使用して派生クラスを作る
class Dog extends runSkilMixin(Animal) {}
class Orca extends swimSkilMixin(Animal) {}

// それぞれのクラスからインスタンスを作成して実行
const animal = new Animal();
animal.execSkil();

const dog = new Dog("shiba");
dog.execSkil();

const orca = new Orca("typeA");
orca.execSkil();

上記のテストコードを実行下のが以下の通りになります。

実行結果
1
2
3
execSkil is not implementation
shiba is run.
typeA is swim.

この場合、基底クラスのexecSkil()をミックスインの関数で、上書きして機能の拡張をしています。
「これってただの継承じゃない?」とここで気が付く・・。

「機能の拡張を目的にするので、基底のクラスにないものを与える必要がある」とここで理解して、テストコードを修正します。

テストコード2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 2種類のmixin用の関数
// 引数に与えられたクラスに、機能拡張する
// 実装を与えるのは、基底クラスにはない関数にする
const runSkilMixin = (Base) => {
class extendedClass extends Base {
run() {
console.log(`${this.name} is run.`);
}
}
return extendedClass;
};
const swimSkilMixin = (Base) => {
class extendedClass extends Base {
swim() {
console.log(`${this.name} is swim.`);
}
}
return extendedClass;
};

// 拡張の基底になるクラス
class Animal {
constructor(name) {
this.name = name;
}
execSkil() {
console.log("execSkil is not implementation");
}
}

// 基底のAnimalをそれぞれの拡張の関数を使用して派生クラスを作る
class Cat extends runSkilMixin(Animal) {}
class Orca extends swimSkilMixin(Animal) {}
// 多重継承させる
class Dog extends runSkilMixin(swimSkilMixin(Animal)) {}

// それぞれのクラスからインスタンスを作成して実行
const animal = new Animal();
animal.execSkil();

const cat = new Cat("American shorthair");
cat.run();

const orca = new Orca("typeA");
orca.swim();

const dog = new Dog("shiba");
dog.run();
dog.swim();

テストコード 2 の実行結果は、以下の通りです。

実行結果2
1
2
3
4
5
execSkil is not implementation
American shorthair is run.
typeA is swim.
shiba is run.
shiba is swim.

クラス拡張を用いた mixin がこれでできていると考えて良さそう。処理の共通化ができました。
Object.assign で実装しているものも見かけたが、今回はここまで。

ここまでで、JavaScript の mixin って「概念」だな理解しました。
(正直、できたけど理解として正しいのか自信はないですが。)

Vue.js の mixin

Vue.js には、機能として mixin が用意されています。
こちらを試していきます。

以下のように mixin の対象を作成します。

src/mixin/test_mixin.js
1
2
3
4
5
6
7
8
9
10
export default {
methods: {
call() {
console.log("mixin object call");
},
},
mounted() {
console.log("mixin object mounted");
},
};

続いて、オブジェクトを次のコンポーネントで mixin します。

src/views/Index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="Index">
<h1>This is index page</h1>
</div>
</template>

<script>
import mixinObject from "../mixin/test_mixin";

export default {
mixins: [mixinObject],
methods: {
call() {
console.log("Vue component call");
},
},
mounted() {
console.log("Vue component mounted");
this.call();
},
};
</script>

こちらのコンポーネントが読み込まれたときのコンソールの表示は、次の通りです。

1
2
3
mixin object mounted
Vue component mounted
Vue component call

こちらを見ると、mounted は順次実行。methods は上書きになっていることがわかります。
ドキュメントでは以下の記述があります。

同じ名前のフック関数はそれら全てが呼び出されるよう配列にマージされます。ミックスインのフックはコンポーネント自身のフック前に呼ばれます。

オブジェクトの値を期待するオプションは、例えば、methods、components、そして directives らは同じオブジェクトにマージされます。コンポーネントオプションはこれらのオブジェクトでキーのコンフリクトがあるとき、優先されます

ドキュメント通り動作していることがわかります。

ここで、具体的な使用例になると、極端に記事が減るように感じます。
「共通の処理をまとめます。」というまとめになっている趣旨の記事はあるんですがね。

その中で、アンチパターンとして、記事がありました。
俺がやらかした Vue mixin のアンチパターンから学ぶ mixin の使い方と代替案

結論から言うと mixin は極力使わない。というところに落ち着いていました。
他記事もスタンスはあまり変わらないように感じます。

例えば、複数のコンポーネントで、同じ API にアクセスしてデータを取得表示する箇所があるとします。
であるなら、それはコンポーネント単位に分割すればいいというのが現在の認識です。

特定のパターンの文字列操作があったとして、それも別ファイルにして import するにしても mixin する必要はなさそうですし。

グローバルなメソッドや読み込み専用の変数を作るための使用例はありますが、これも mixin である必要はないかなと感じました。

作るものの規模次第で、認識が改まる可能性はあります。

ではでは。