#前書き
今回のモノでは、
・可読性向上のためファイル分離
・コンポーネント指向を生かし共通部分を極力排除
・保守性向上のためのルール,mixin化
を目標にやっていきます
#コンポーネント指向
コンポーネント指向参考記事
アトミックデザイン参考記事
まず前提としてコンポーネント指向/アトミックデザインを目指していこうね。
というのがこのサービス開発における方向性です。私も社員から教えられ実際この考えに賛同し、趣味プロの中でも時間があればこういう方向性で開発/リファクタリングを進めてます。
ざっくり説明すると(間違ってたらスイマセン)、
ソースコード(ex:HogeHogeButton.vue)を入力と出力を依存なく作成しそれをそれのみでも動くように作り、可能な範囲で小さく作成することにより、atom(原子)を目指しもう依存なく動くパーツを作成したあと組み合わせていくとコスパ良いコードだよねーって感じです。
#オブジェクト指向
java,C++はオブジェクト指向だ。とかjsは関数型だ、
オブジェクト指向だとか話したいわけではなく、オブジェクト指向の継承や木構造で表されるその様子についてフワフワとした理解があれば大丈夫です。
#実際の開発されているコードの現場
人の入れ替わりも多少激しい会社ですので最初からみんなが、
そのような開発をしていたわけではありません。
アトミックデザインを最初から目指してコーディングしていたわけではないので、
一つのファイルにどでかい部品(決してatomでない)が2~3個搭載されており、
またそのファイル以外にも似たようなどでかい部品が存在しているが、似ているが,決して同様なコードではないことが多々あると思います。(私のところはそうでした)
また、リファクタリングの工程を区切り区切りで進捗を出し、長い作業をし手が離せないというのはリファクタリングの現場ではあまりよろしくないなーともありここからそのままのatomicなコンポーネント指向にもっていくのは少々ミスマッチな気がします。
では実際にここから、私が行った工程を順を追ってやっていきます。
擬似ファイル構成(以下、hogehogeproject)
view/
|-hoge1.vue
|-hoge2.vue
|-hoge3.vue
components/
|
...
|
##部品の分離
一つのファイルにどでかい部品があるわけですので、可読性が著しく悪いです。
せっかくvue.jsというコンポーネント指向なフレームワークを使っているので、どうにか分けて動作していくことを目指していきたいです。
どうやら今回のhogehogeprojectではbigHoge
という部品がhoge1,hoge2,hoge3に存在してそうだなというのが分かりました。(実際には名称がついてないこともあると思います。共通したコード群としてbigHoge
とします)
依存を取り除き、一つのコンポーネントとして分けることが可能そうなので、props等を使いhoge1,hoge2,hoge3からbigHoge1,bigHoge2,bigHoge3を分けます。
(propsや$emit等で頑張りましょう)
view/
|-hoge1.vue
|-hoge2.vue
|-hoge3.vue
components/
|-bigHoge1.vue
|-bigHoge2.vue
|-bigHoge3.vue
##部品の共通部分排除
ここが肝です。
共通部分を持っているのに分けられている部品はかなり厄介です。
そこの共通部分が修正対象になった時に、n倍コード量かかりそうで、厄介です。
そこで共通部分を出来るだけ排除したいとみなさん思うでしょうが、
どうすれば良いでしょうか?
アトミックデザインにし、共通部分は共通のコンポーネントのみでしか記入されないとすると、
上手く改善は出来そうですが、今からするには全てに対して行う必要がありかなり重たいタスク(手が止められない)になりそうです。
ここで3つの共通部分を持ったコンポーネントを作ったら、そこに対しては改善出来そうです。
ただこの方法には弱点があり、bigHoge1とbigHoge2の方がbigHoge1とbigHoge3より共通部分が多そうな場合共通部分を残したまま共通コンポーネントを作成することになり、もしこれが3つではなくn個の場合、かなり余剰な共通部分は残ってしまいそうです。
###似ているコンポーネントの共通Wrapperを作成する
ここで、共通部分を排除することを目的とした木構造を考えます。
wrapperBigHoge
|-bigHoge1
|-bigHoge2
|-bigHoge3
の恩恵は受けたいですが、
wrapperBigHoge
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
|-bigHoge3
このように、オブジェクト指向の継承のように、
入れ子の構造を守った場合、wrapperBigHogeの恩恵はそのまま受けいられそうです。
そこで下記のような構造を考えます。
wrapperBigHoge
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
|-wrapperBigHoge2
|-bigHoge3
このようにしても、
bigHoge1,bigHoge2,bigHoge3は、wrapperBigHogeの恩恵を受けいられますし、
先の問題だった余剰な共通部分が存在する問題も、この親子関係を作っていくとした場合階層の違いは存在しても、マクロでの共通部分、ミクロでの共通部分と分けて考えることができそうです。
また、この方法の一番の利点は共通部分排除の段階では、
自分の親と対象にする子供のみを考えれば良いという点にあると思います。
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
views/
|-hoge3.vue
|-hoge4.vue
このように、とりあえず、bigHoge1,bigHoge2をhoge1,hoge2から分離し
その共通コンポーネント"wrapperBigHoge1"を作成することができました。
また、次に
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
|-wrapperBigHoge2
|-bigHoge3
|-bigHoge4
として、既存のwrapperBigHoge1に将来的に統合できそうなことを意識して、
wrapperBigHoge2を作成します。
次に、
wrapperBigHoge
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
|-wrapperBigHoge2
|-bigHoge3
|-bigHoge4
とし、wrapperBigHogeを作成とすることができ、
小さな領域で開発パフォーマンスを改善することができ、また如何様な拡張性ももっており
自由度高くリファクタリングをしていくことが可能なので、
コンポーネント指向で大事なそれが部品として動くという要素を残しつつ、いずれ時がきた時に上位(親)のwrapperを作成することも視野に入れる
ことを考え開発していくとスムーズにできます
先の段階で、
将来的に木構造のような深さがn(ある程度大きい値)になることが、予想されます。
いま開発している段階で、深さが最大2ぐらいだからemit
で貰ったものを、親にごちゃごちゃして返せばよいか...
という発想だといずれそれをリファクタリングする工数が増えるでしょう
現状のvueで親子関係の疎通はrefとemit,props
が代表的なのであると思います。
個人的にはemit
とprops
で作成していくことをおすすめしますが、
propsに関しては親から貰った要素をそのまま子にpropsとして渡すことは容易だと思いますが、emitに関してはそうはいきません。
emitは親に命令(以降order)とvalueを渡すことで、親が特定のイベントを発火させることができます。
ただこの場合親から貰ったemitをそのまま子に発火させにいくのは多少手間が必要(methodsに記入して,そこに発火させてそのmethodsの中に命令とvalueを渡してー...という感じです)
(一行で済ませようとすると、valueは取れても、orderはとれません
ex: @hoge="emit("hoge", value)", @hoge="emit" 等もできない)
そこで以下のことを考えます。
・普段コンポーネントを作成しているように、emitを呼び出したい
・普段コンポーネントから貰ってるvalueをそのまま受け取りたい
・emitの受け流しは極力コードを記入せずに済ませたい
これを満たすようにfunctionを作成していくと,
start (order, value) {
let pack = {}
pack.order = order
pack.value = value
this.propagate(pack)
}
propagate (value) {
if (value.order) {
this.$emit(value.order, value)
}
}
end (value) {
this.$emit(value.order, value.value)
}
これをすることで、いつもemitしてたところを、
startに、伝播をpropagateに、最後通常通りに使用したいので伝播されたものをendでemitすると、
考えていたことを実現することができ、
これをmixin化することで、全ての木構造のコンポーネントに対して適用することができます。
mixinについての詳しい話は参考記事を見ていただきたいですが、今回のだと例えば
export {
methods: {
start (order, value) {
let pack = {}
pack.order = order
pack.value = value
this.propagate(pack)
}
propagate (value) {
if (value.order) {
this.$emit(value.order, value)
}
}
end (value) {
this.$emit(value.order, value.value)
}
}
}
とし、全ての木構造コンポーネントにmixins:[HogeHogemixin]
とすれば動くと思われます。
##ルール決め
木構造なコンポーネントを作成してきましたが、
ある程度ルールを作成するとスムーズにこれからの開発ライフを楽しむことができます。
ここで属性の定義をします。
wrapper: 子供、親と自由に持つことが出来るが通常のコンポーネントとのように、子供以外から自由に呼び出されたりしてはいけない
instance: 子供を持つことを推奨しない(禁止ではない)がこの木構造に含まれていないvueファイルから呼び出されることを前提に作られる
この二つを定義することで、前にやったwrapper化したファイル等は属性で表すとこのようになります。
wrapperBigHoge
|-wrapperBigHoge1
|-bigHoge1
|-bigHoge2
|-wrapperBigHoge2
|-bigHoge3
|-bigHoge4
|
V
wrapper
|-wrapper
|-instance
|-instance
|-wrapper
|-instance
|-instance
となります。
このように分けることで、
wrapperはstart,propagateのみを持つ
instanceはendのみを持っている状態が好ましい
と出来るので、どこかで伝播が止まっているな?という事例が少なくなります。