後悔しないためのVueコンポーネント設計
posted with ヨメレバ
中島直博 インプレスR&D 2019年06月
書籍「後悔しないためのVueコンポーネント設計」のまとめ。
点数
75点
感想
コンポーネントの設計がわかりやすく、論理的に説明されていた。
テストしやすいコンポーネントは「機能が少なく状態を持たない」というのは間違いないが、その分作成コストがかかってしまう。
ちょうどいい粒度にコンポーネントを分割することは、なかなか難しいことだと思う。
テストしやすいコンポーネント
テストしやすいコンポーネントとは
- 機能が少ない
- 状態を持たない
- dataはコンポーネントに閉じた状態だけを扱う。
(表示非表示の切り替え用フラグやフォームの入力項目など) - propsで渡されたmodeやtypeといった値によって動作が変わるコンポーネントにしない。
ex)if (mode === 'edit')
やv-if="mode === 'edit'"
例えばpropsで受け取った値によってa要素とbutton要素を切り替えるコンポーネントは、見た目が似ていても役割が違うので別のコンポーネントにするべき。
作成時にコストがかかったとしても拡張や修正時にそれ以上のリターンが得られると筆者は考えている。 - VuexやVue Routerなどへの依存が少ない。
例えば「VuexのStoreから取得した商品データをリンク付きで表示し、クリックされたらVue Routerで詳細画面に遷移させる」という機能のコンポーネントは流用が難しい。
「商品データをリンク付きで表示する」機能は別コンポーネントにすることで流用可能となる。
<div v-for="item in items" :key="item.id" >
<span>{{item.label}}</span>
<button @click="clickItem(item)">{{item.label}}</button>
</div>
は
<Item v-for="item in items" :key="item.id" v-bind="item" @clickItem="onClickItem" />
とし、Itemコンポーネントで以下のようにする。
<span>{{label}}</span>
<button @click="clickItem">{{label}}</button>
Propsの型指定
- JSONでも表現可能である以下の型だけを型指定で使うべき。
Number, String, Array, Object, Boolean
例えば型指定でDateを指定すると、JSONから取得した日付文字列をコンポーネントに渡す際にnew Date()や日時操作系のライブラリでの変換が必要になり、テストが複雑になってしまう。
日付はStringかNumberで受け取り、表示の際に変換するべき。
子から親への伝達
- 2種類の書き方がある。
- $emitで親にイベントを伝えて、親のv-onでそのイベント受け取り関数を実行する。
<Child v-on:clickChild="onClickChild"/>
- propsでFunctionを受け取り、子でその関数を実行する。
<Child :clickHandler="clickHandler"/>
- $emitで親にイベントを伝えて、親のv-onでそのイベント受け取り関数を実行する。
- 著者はプロジェクト内で統一さえれていればどちらの書き方でもいいとしているが、「Propsの型指定」のことを考慮すると$emitを使うべきだと思う。
ライフサイクルフックに処理を書かない
- vue-test~utilsはライフライクルフックをモックに差し替えることができないため、ライフサイクルフック内に直接処理を書いてしまうとテストを妨げる要因となってしまう。
- 面倒だが、ライフサイクルフック内でmethodsに定義した関数を実行するようにするべき。
mounted() {
this.hoge();
}
コンポーネントの分類
コンポーネントの分類パターン
- Presentational ComponentとContainer Componentに分けることで、見た目とロジックを切り分けることができる。
ただし、保守性が良くなる反面、コードが肥大化というデメリットもある。
Presentational Component
- 見た目を担当するコンポーネント。ロジックには関与しない。
- 主にpropsで受け取った値を表示する役割を担う。
- アクションが起こった場合は$emitを使って上位コンポーネントに処理を委ねる。
Container Component
- ロジックを担当するコンポーネント。
- データをprops経由でコンポーネントに渡し、コンポーネントからのイベントでStoreのcommitやRouterの機能を呼び出す。
ディレクトリ構成
筆者のディレクトリ構成
- コンポーネントを以下の4つのディレクトリに格納している。
basics
- 単体で機能が成立する最小単位のコンポーネント。
- ロゴ、検索入力欄、ユーザーアイコン、など。
components
- basicまたはcomponentを組み合わせるためのコンポーネント。
- メニュー、矢印アイコンとクリックで開くメニューのセット、など。
containers
- basicとcomponentにデータを渡すためのコンポーネント。
- Storeを直接参照できる、basicとcomponentのレイアウトを行う、などの特徴を持つ。
- ヘッダー、など。
pages
- Vue Routerのroutesに指定するコンポーネント。
- Storeを直接参照できる、containerのレイアウトを行う、などの特徴を持つ。