うしろのこの本ください

なんでもかきます

Vueのmixinsはなぜ辛かったのか

TL;DR

ES6 modules(import/export)が優秀なのでこれで良いと思う。むりに難しくする必要はない。

Vue3 からはcomposition apiを使えるけどこれもES6 modulesベースで使っていくことになる。ReactのContext的なことがしたいならinject/provideを使おう。


自分は今までいろいろな理由でvueのmixinsを避けてたけど、最近転職して強制的に目の当たりにしたため改めてmixinsの何が自分にとって辛いものなのかを書いてみようと思った。ちなみにすでに社のslackでお気持ち表明済みで、いい反応をもらったことだしなんとかしていこうとなっている(なっただけ)。

thisがmixinの中にいる

mixinsはプロパティオプションをコンポーネント間で使い回す(マージする)ためにオプション丸ごと外部定義する機能だが、普通に運用していればいずれthisが生える。コンポーネント内ではどのmixinに依存しているのかは見た目でわかるが、どのプロパティに依存しているかはわからない。特にテンプレート内ではthisすら書かないので、この値はどこからきてるんだろう?と言う時にファイル内で検索してもでてこないし、ファイルジャンプもできない(エディタによっては魔力で飛べるかも)。ts化することで解決もできるが、後述の理由でそれもしんどい。

そもそもコンポーネント間で共有したいのはオプションではなく処理そのものだし、SFCにmethodsとかcomputedとかdataとか書きたくない!って需要ではない(いるかもしれないけど)からmixinは大仰なことが多い。thisから生えるものの名前は衝突しやすく同じ名前だけど別の型&初期値というのはよくある話(formDataとかhandleSubmitとか)で、いちいちユニークになるようにプレフィックスとしてmixinごとの名前をつけることもできるが、自分は名前をつけることは境界を定めることで、一意なプレフィックスをつける時点で共有すべきものじゃないと思っているのでいい使い方とは思わない。マージされて欲しい時とそうでない時でコントロールできれば多少マシになるかもしれないけど、事故が怖い。

一番辛いのが複数のmixinを継承している時で、mixinA、mixinBがある時新規に作ったコンポーネントに諸事情でmixinCを追加して名前が衝突するパターン。極端な例で言うとCとBが衝突しないように修正すると、AとBがその影響によって衝突してしまいAも修正するが、今度はAに依存している別のコンポーネントが…みたいな状況になりうる。これはライブラリがmixinを用いている時にでも起こりうるので、単にプロダクト内で気をつけていればいいという問題でもない。影響範囲の読みにくさが大きな割れ窓になることがある。

型の付け方が冗長

こんな感じになる。せっかくVue3からtsフレンドリーになるのにこれを続けるのは辛い。

(this as IncetanceType<typeof hogeMixin>).somethingMethod()

ES6 modulesならアサーションなんてしなくてもいいし、Vue.extendしていればcomputedでもreturnの型を書けばいいだけだし低コスト&高拡張性でVSCodeにも優しい。

import { somethingMethod, somethingComputed as someCom, SomethingComputed } from './something'

computed: {
  someCom(): SomethingComputed {
    return someCom(this.val)
  }
},
methods: {
  somethingMethod(val) {
    return somethingMethod(val)
  }
}

mixinの問題である以下の点をすべて解消できる

  • どこからきている値かわかりにくい -> importで明確
  • 型付けが冗長 -> アサーションを用いずに解決できる
  • 名前の競合 -> importの時点で別名をつけられる(あんまりよくない)

また、Vue3から使えるcomposition apiは関数ベースでES modules前提で使うことになるし、そもそもsetupからmixinでマージしたプロパティにアクセスすることはできない。もとからES modulesで運用していればcompositionへの移行もスムーズにできてお得。mixinsは引き続き使えるが、役目を終えた印象。

おわり

mixinsは一見処理を抽象化して共有できる便利な機能に見えるが、DXの悪化具合が激しくて、言語機能やエディタによる支援もまともに受けられないことが足枷になっていてプロダクションで扱うには厳しいかなと思う。import/exportがポータビリティと拡張性の高さで圧勝なので、無理にVueライクになる必要はなく、Vueもjsなんだと割り切っていくことも大切な気がする。