Slide 1

Slide 1 text

vueで中規模以上のフロン トエンドを組んでいて 役に立ったtips

Slide 2

Slide 2 text

自己紹介 張 たいよ (GitHub: @neutron63zf) 東京大学理学部物理学科 4年 ● ventus-inc ○ JavaScript ( Vue.js / Nuxt.js ) ○ Golang / Firebase ● (元)東京大学五月祭常任委員会 ○ AWS / Nginx / Docker ○ Node.js ( Express )

Slide 3

Slide 3 text

作っているサービス(whooop!) ● スポーツチームの発行したカード を買うことで応援できる ● カードを買うことでイベントなどの さまざまな特典が得られる ● オークションなどで、ユーザー間 でデータをやりとりできる

Slide 4

Slide 4 text

構成 ● 動機 ● 通信の一本化 ● vuexの構成 ● 各コンポーネントとデータフロー ● 実際にやってみて・まとめ

Slide 5

Slide 5 text

前提知識 ● Vue ○ 表示をコンポーネントという単位に分割して書くことができる ● Vuex ○ (すごくざっくり言うと) Storeというものに状態をまとめ、それに対して操作をする。 ○ 「一方向のデータフロー」を課すことで、挙動を予測しやすくする。 ● Nuxt ○ Vueでサーバーサイド・レンダリング( SSR)をするときに便利なフレームワーク ○ ほとんど普通の Vueを書く感覚でSSRができる反面、制約がきついところも

Slide 6

Slide 6 text

動機:「いつの間にかアプリケーションが大きく...」 ● whooopは当初、ページは20ほど、エンドポイントは40ほどのサービスだった。 ● しかし、いつの間にか機能の増加とともに、ページ数もエンドポイントも倍以上に! ○ 現在はエンドポイント 100近く(統合中...)、ページ数50ページ程度 ● 当初のコードではだんだん改良、機能追加が厳しくなっていった アプリケーションが想定を超えて大きくなり、当初の書き方だと通用しなくなった

Slide 7

Slide 7 text

例(1): リクエスト処理が不明確 ● APIで通信する際、上はuserIdを与えればいいということ がわかるが、下はAPI ドキュメントを読まないと、何を与え るべきか不明 ● 生でHTTP Methodやurl、パラメーターを書いていて、そ れが至るところにあるので、変更が大変

Slide 8

Slide 8 text

例(2): データ変換・保持がまちまち ● teamsとteamlistみたいに、名前がかぶっていたり、通信 後にキャメルケースに変換しているか否かがまちまち ● どのようなデータ構造でストアに保管されているかはスト アごとに異なり、新しく追加されるものについても、「今ま でのステートに統合される」のか「今までのステートを上 書きする」のかが異なる

Slide 9

Slide 9 text

結論:しんどい ● ストアが使いづらいことこの上ないので、ストアのステートからではなく、毎回ストアのアクションの返り値を 直接コンポーネントにセットするコードが多発 ○ ストアを使う意味 ... ● 上と並行して、ストアにデータを記録することなくレスポンスをパースするだけのコードも多発 ● たまに変なデータ変換をかけている部分は逆変換をかけないと使えないことも →デザインのリニューアルついでに大幅にリファクタリングするぞ! (このスライドはその時にいろいろ試した施策をいくつか抜粋して紹介している)

Slide 10

Slide 10 text

通信の一本化

Slide 11

Slide 11 text

△通信したい ○通信して〇〇したい そもそもフロントから通信をするときに、いちいち postかgetかput か。urlはどこか、どこのパラメーターにどのデータをどんな形式で 入れるかは気にしたくない。 そういうのはいちいち意識せずに済むように、 関数でリクエスト形 式等の内部構造はある程度ラップする。 ラップした関数は集めておいて、そこからだけ使うようにする。 呆れるほど単純だが、これを徹底するだけで、そうでない場合に 比べてかなり見通しはよくなる。

Slide 12

Slide 12 text

APIドキュメントから自動生成 APIの数が増えてくると、単純に先程のようなコードを錬成するの がめんどくさくなってくるので、 Open APIや、API Blueprintといっ た、ある程度まとまったAPIドキュメントがある場合は、そちらから APIクライアントを自動で作ると楽。 例えば、Swaggerの場合はSwagger-Clientから、自動でSwagger ドキュメントを読み込み、APIクライアントを動的に作れる。 (一瞬でつなぎ込みが終わりちょっとした全能感に浸れる)

Slide 13

Slide 13 text

Vuexの構成

Slide 14

Slide 14 text

APIデータ保持ストアの分離 ● propsのバケツリレーの緩和 ● ページをまたいだ状態の保持 ● APIから取得したデータの保持 最初の2つは、ユースケースごとにモジュールを作ってもそんなに荒れな い。 しかし、通信(つまり3つ目)は統一されていたほうがアプリケーションが作 りやすいのでちゃんと考える。

Slide 15

Slide 15 text

ネストされたデータの展開(1) APIリクエストを扱っていると、あるオブジェクトのプロパティーに関連 するオブジェクトが入っていることがよくある。(例: articleのcomments プロパティーに、commentというオブジェクトが配列で入っている) だが、normalizrというライブラリを使うと右のように、オブジェクトの種 類ごとに展開でき、「key-value」ごとにアクセスできる形式に変換でき る。

Slide 16

Slide 16 text

ネストされたデータの展開(2) これにより、オブジェクトの種類ごとにストアを作成し、 normalizrに より分解されたデータを入れることにより、ストアの中のデータの入 り方を統一することができるようになる。 なお、normalizrでは、何も指定しないとidでしかアクセスできるよう にならないが、他のキーでもアクセスしたい場合は、そのキーと idの 対応表を作成しておくとよい。

Slide 17

Slide 17 text

APIストアでやること 以上のことをまとめると、APIストアでやることは以下の通り。 ● (初期化時)オブジェクトの種類だけ配下のモジュールを動的に登録する。 ● リクエストのアクションがdispatchされたら、通信を実行 ● レスポンスをnormalizrにかけて、各モジュールに分配

Slide 18

Slide 18 text

APIストアでやること(実際のコード例) 2行目でリクエストを実行し、 3行目でnormalizrにかけて、 5行目でストアに保存

Slide 19

Slide 19 text

(余談)リクエストを送るメソッドの追加 whooopでは、リクエストを送る際はストアのリクエストアク ションをディスパッチすればいいようにしてあるが、毎回 「this.$store.dispatch」、なんて書いているのは正直めんど くさい。 なので、vueのインスタンスに「$apiRequest」を、nuxtのコ ンテキストに「apiRequest」をプラグインし、それでリクエスト が送れるようにしてある。

Slide 20

Slide 20 text

viewに依存するストアの部分 基本的に注意すべき部分はあまりないが、以下の 2ルールだけ課した。 ● ステートは最低限必要なものにすること(何でもかんでもつめこまない) ○ ページをまたがって引き継ぐ必要のあるデータ。そして、 props渡しだと不可能なデータの運搬(モーダルなど) ○ そうで無いデータはコンポーネントのステートとして処理できるので、あまりストアの旨味がない ● アクション・ミューテーションも最低限必要なものにすること ○ つまり、複数のアクションのうち共通する処理であっても、ストアの内部でしか使われないのならば、「アクショ ン」や「ミューテーション」ではなく、関数として切り出す 特に2つ目のルールは、ストアが大きくなったときにスパゲッティーコード化することを多少は緩和してくれる。

Slide 21

Slide 21 text

各コンポーネントと データフロー

Slide 22

Slide 22 text

Atomic Designを導入する(1) デザインをリニューアルする際に、「 Atomic Design」 を導入し、コンポーネントの再利用性を高めることにし た。 だが、右の図にあるような「よくネットである Atomic Design」の他にも、会社ごとにたくさんの「オレオレ Atomic Design」なるものがあり、どれもよさげで迷っ た。 結局、厳密でなくても、「階層的にコンポーネントを構 築していく」というコンセプトの元で自分たちなりにルー ルを決めることにした。

Slide 23

Slide 23 text

Atomic Designを導入する(2) 悩んだ末、「Templates」の代わりに、「Layouts」という 層を「Organisms」と「Pages」の間に置くことにした。 そして、以下のルールを課した。 ● それぞれの層のコンポーネントは、それよりも 下層のコンポーネントだけから作られること。 ● Organisms以上の層では、CSSを基本的には 書かず、クラスをつけるなどで対処する。 代わりに 「Layouts」 を入れる

Slide 24

Slide 24 text

CSSとAtomic Design Vueでは単一ファイルコンポーネント内で CSSを書くこと ができる。 だが、Organism層以上では基本的にはそれを使用せ ずに、位置の微調整などはクラスをつけることで行っ た。(例:flexボックスにしたい場合はflexクラスをつけ る) これにより、CSSの重複などを防ぐことができるほか、 コーディングの指針が明確になる 。(CSSがどうしても欲 しいなら下の層にうつすか、新たにクラスを生やす。)

Slide 25

Slide 25 text

どの層かの判断基準 Atomic Designでおそらくいちばん困るのが 「このコンポーネントはどの層に属するのか」という判断 で、実際 に運用をしたい場合はここを ある程度スムーズに判断できるような基準を設けておく とかなりやりやすい。 whooopでは現在以下のように区分けしている。 ● Atoms … これ以上分解すると機能として成り立たない単位(チェックボックス等) ● Molecules … 特定のオブジェクトに依存しないが、 Atomsを複数組み合わせて実現できる単位(カ ルーセルや、検索窓等) ● Organisms … 特定の種類の、単一のオブジェクトを表示する単位(ユーザーの持つカード等) ● Layouts … 複数種類や、複数個のオブジェクトを表示する単位(ユーザーの持つカードの一覧)

Slide 26

Slide 26 text

OrganismsとLayoutsの例 右の画像は「チームの中で、どれくらいカードを集めている かのランキング」という「Layouts」 その中の1行1行が「ユーザーが何位で、何枚カードを持っ ているか」を表示する「Organisms」

Slide 27

Slide 27 text

コンポーネントによるリクエストの構築 vueのコンポーネントがbuildRequestというオプションを持 てるようにして、リクエストを各コンポーネントで構築できるよ うにした。 これにより、「使っているコンポーネントにそぐわないリクエス トを送ってしまう」という事態をかなり減らせる。 なお、そのまま送らないのは、 nuxtのSSR時は、基本 pagesからしかリクエストを送れないため

Slide 28

Slide 28 text

コンポーネントによるリクエストの構築(2) VueのScoped Slotsという機能を用いてリクエストを送っ て、子のコンポーネントに送るだけのコンポーネントを作る と いう手法もかなり有効。 これにより、getリクエストに関しては宣言的に書くことができ るだけでなく、loadingやerror時の分岐など、状態に由来す る処理を共通化することができる。 「ダミーデータで表示している部分で実際に api由来のデー タを使うようにする」という書き換えが一瞬で終わる。

Slide 29

Slide 29 text

ストアのデータの取得(1) サーバーから取得したデータはストアに入っているわけだ が、そのままでは取得するのが少し面倒。 そこで、オブジェクトの種類ごとに 「idや、idの配列から簡単 にストアをクエリできる関数」 を作っておくとストアから取得す る部分が随分すっきりする。 whooopでは、(normalizrのために定義した)オブジェクトの 一覧から、ストアだけでなく、こうしたヘルパー関数も自動で 作っている。

Slide 30

Slide 30 text

ストアのデータの取得(2) また、normalizrを通すとネストされたデータは idに変換され てしまうので、ヘルパー関数でそれらを再構築できるように するオプションも実装した。(右の画像では、ストアからオー クションの配列を取得し、ordersというキーについて、またス トアから注文の配列を取得し、付加している。) とはいえ、これを使わずに実装できるケースがほとんど。

Slide 31

Slide 31 text

実際にやってみて ・まとめ

Slide 32

Slide 32 text

実際にやってみて(1) 快適!! フロントエンドで面倒になりがちな ● CSS ● つなぎ込み の2つの負担をかなり減らせる。そして機能に集中してコードを書くことができるようになる。 それなりに準備は大変だったがおすすめできる。

Slide 33

Slide 33 text

実際にやってみて(2) 一方で、Atomic Designはともかく、リクエストを送る部分はかなり「オレオレフレームワーク」になってしまった 感が否めない。(通常のvue, vuexの上に積み上げたものが大きい) そのため、はじめて入ったメンバーに対しては、説明をした上で、比較的小さな、しかしこれらを一通り経験で きるようなタスクを振ることで慣れさせる期間を設けている。

Slide 34

Slide 34 text

まとめ ● APIリクエストは、http methodなどをラップして一箇所にまとめておく ● APIデータをストアに出し入れする方法は用意しておくと楽 ● 表示などで使うストアは、ステートもアクションもミューテーションも最低限に抑える ● Atomic Designをやるなら階層を分ける基準は明確にしておいた方がいい ● コンポーネントがリクエストを構築できるようにしておくとよい