Vue.jsアーキテクチャリング勉強会 https://cw-engineers.connpass.com/event/146975/
で話したスライドです
© - BASE, Inc.BASEにおけるVueコンポーネント設計の現在
View Slide
⾃⼰紹介© - BASE, Inc.松原 佑介フロントエンドエンジニア2018年9⽉BASE⼊社: simezi
BASEのプロダクト© - BASE, Inc.ネットショップ作成サービス「BASE」ショッピングアプリ「BASE」Payment to the People,Power to the PeopleMISSION
© - BASE, Inc.2019年10⽉25⽇マザーズ上場しました
BASEのフロントエンド開発の現状© - BASE, Inc.サーバーサイド:PHP, Goフロントエンドエンジニア:6名フロントエンド:Vue.js, TypeScript, Sketchデザイナー:8名(2019年10⽉時点)
フロントエンドチームは何をしてきたか© - BASE, Inc.• 管理画⾯刷新プロジェクト開始でjQueryからVue.js+TS化• SPAではなく、⼤きな機能単位でMPA(Multi Page Application)化• 内製のUIコンポーネントライブラリの作成࣍ͷ5Λࢧ͑ΔVue.jsUIίϯϙʔωϯτϥΠϒϥϦΛҭͯΔ
今ではプロジェクト開始から約1年半© - BASE, Inc.本⽇はそこで追加で得られたコンポーネントづくりの知⾒について話します
おしながき© - BASE, Inc.BASEでのコンポーネント設計Storybookの整備個別のコンポーネントの実装
© - BASE, Inc.①BASEでのコンポーネント設計
コンポーネントの種類© - BASE, Inc.Atomic DesignとContainer Componentの考え⽅を借りて、役割に応じて⼤きく4つに分類. Container Component. Presentational Component. Common Presentational Component. Atom Component ( UIライブラリ )
コンポーネントの種類© - BASE, Inc.コンポーネントを役割に応じて⼤きく4つに分類. Container Component. Presentational Component. Common Presentational Component. Atom Component ( UIライブラリ )
Container Component© - BASE, Inc.• Atomic DesignでいうPageに相当• API通信、Storeへのアクセスを⾏うことができる 唯⼀のコンポーネント• 複数のPresentational ComponentやContainerComponentを束ねる役割• ContainerComponentの中に多少のUIを持つことは認める
Presentational Component© - BASE, Inc.• Template/Organismに相当• 副作⽤を持たず、与えられたプロパティを表⽰するだけのコンポーネント• 具体的なビジネスロジック‧表現に紐づくため再利⽤性は考えないコンポーネント• ContainerComponentとPresentationalComponentはディレクトリを分けず、名前で区別する
Common Presentational Component© - BASE, Inc.• Template / Organism / Moleculeに相当• Presentational Componentの中でアプリケーションの内部で頻出するデザインのパターンを定義する• 例)全体のヘッダーやページのパンくずとタイトル• 具体的なコンテキストがない抽象的で使い回しができるコンポーネント群
Atom Component© - BASE, Inc.• Atomic DesignでいうMolecule/Atomに相当• 個別のアプリケーションにとどまらず、サービス全体で流⽤できるような⼩さいコンポーネント群• form部品やbuttonなどの利⽤頻度がたかいもの• スタイルの局所的な上書きも許容できるようにScoped CSS / CSS Modulesを使わない• コンポーネント外との関係を決めるスタイルは当てない 例) margin, float, z-index‧‧‧etc
概念図© - BASE, Inc.
概念図© - BASE, Inc.ΠϕϯτͷྲྀΕ
概念図© - BASE, Inc.֎෦ͱͷ௨৴Container͚ͩͰߦ͏
概念図© - BASE, Inc.Common/PresentationalͲͪΒͰʹͳΕΔ
© - BASE, Inc.②Storybookの扱い
Storybookに求められる2つの役割© - BASE, Inc..サンドボックス 実際のコードに対してどう動くかを試す環境 実装の際のエンジニア向けガイドとしての働き.カタログ 提供されているコンポーネントを網羅的に⾒るどちらかの要素が⽋けたものが出来上がりがち
サンドボックス性の⾜りないStorybook© - BASE, Inc.昔ながらのUIカタログの機能しかないケース• やりたいことはわかっても渡すべきprops、キャッチすべきevent、他コンポーネントとの組み合わせなど、実装に必要な情報が⾜りない• storybookに置く意味があまりない• 実装を意識しないならsketch上にあるだけでいい
カタログ性の⾜りないStorybook© - BASE, Inc.実装者が作るだけ作って満⾜してしまったケース• コンポーネントが⼀個おいてあるだけ想定されている使い⽅が伝わらない• knobsに⼤量のパラメータパラメータの組み合わせもわからず、作った⼈にしかわからない。特にデザイナーとはコミュニケーションが難しい協業の場として機能しない
storybookを機能させるために© - BASE, Inc.• Knobsに頼りすぎない• あくまでknobsは補助• UIとして利⽤するパターンはなるべく⼀覧できるように(= カタログ)• 実装に必要な情報はきちんと書く(=サンドボックス)• コンポーネントごとにREADME.mdを⽤意する
コンポーネントごとのREADME.md© - BASE, Inc.@storybook/addon-notesで読み込む必要な情報• 簡単なサンプルコード• 動作の仕様• Propsの値、型、デフォルト値• Eventの発⽕タイミング、payload• slotの名前• scoped-slotで渡される値 …etc将来的には@storybook/addon-docsでmarkdownを活⽤できるようになるはず(現在はVueのサポートがWIP)
コンポーネントのレイヤ別に必要なこと© - BASE, Inc.ίϯϙʔωϯτͷछྨ Χλϩάੑ αϯυϘοΫεੑContainerStorybook্ʹొ͠ͳ͍Presentational Common ߴAtom ߴ ߴレイヤの低い抽象的なコンポーネントほど説明が必要
© - BASE, Inc.③個別のコンポーネントの実装
フロントエンドの性質© - BASE, Inc.• フロントエンドのコードはライフサイクルが短い• 修正=全取替であることが珍しくない• ドメインロジックに落としづらい細かい分岐の連続• 例)ある⼀箇所だけ「0円」を「無料」にしたい…• 共通化しても仕様がすぐに変わってしまう
© - BASE, Inc.コンポーネントで作る最⼤のメリットは共通化ではなく責務の分離
コンポーネントの責務の分離© - BASE, Inc.• コンポーネントで作ることでスタイル、マークアップ、スクリプトを外部に影響しない形でカプセル化できる• 作る側もとにかく疎結合を優先して、類似コードの重複を恐れない。• 本当に必要なパターンと確定したら共通化する• いつでもコードの塊で捨てられる状態をキープする
実装のtips① props© - BASE, Inc.• ObjectやArrayを型に指定するのはなるべく避ける 特にTSを採⽤している場合にはPropTypeを使うprops: {deliveryInfo: {type : Array as PropType},payment: {type : String as PropType,}},
実装のtips① props© - BASE, Inc.• HTML標準に沿った名前、汎⽤的な名前をつかうίϯϙʔωϯτར༻ऀ͔ΒΈͨࣗવͳΠϯλʔϑΣΠεΛ৺͕͚Δname: 'AppInput',props: {id: String,name: String,type: String,value: {default: '',type: [String, Number],},placeholder: String,}
実装のtips②event© - BASE, Inc.• イベント名にカラフルな語彙を使わないBASEͰ {Πϕϯτͷಈࢺ}:{Πϕϯτͷର} ͱ͍͏ϑΥʔϚοτΛར༻͍ͯ͠Δྫ) update:someValue• XMLͷ໊લۭؒͷϑΥʔϚοτʹ߹Θͤͯಈࢺ+(͋Εతޠ)ͷܗࣜ• ಈࢺͷޠኮΛߜΓγϯϓϧͳ୯ޠΛ͏͜ͱͰݟ௨͕͕͋͠Δ• ಛʹಈࢺίϯϙʔωϯτʹͨ͠Πϕϯτ໊Λ͚ͭͳ͍@click:search="openSearchBox"@change:all="checkAll"@change:sortOrder="onChangeSortOrder"@change:editMode="onChangeEditMode"@change:keyword="onChangeKeyword"@search="onSearch">
実装のtips②event© - BASE, Inc.• イベントのpayloadにObjectを使う• RORO(Receive an object, return an object)• emitするプロパティが増えても壊れにくい// emit͢Δଆthis.$emit('change', { enabled: defaults, dirty: false })// ड͚औΔଆɹupdateShippingFee({ enabled, dirty }) { // ObjectͷׂೖͰड͚औΔthis.$store.useCase(new EditItemDetail()).updateShippingFee(enabled, dirty)}
実装のtips③style© - BASE, Inc.• classを条件分岐や処理で渋滞させない• すべてclassで表現するとパターンが増えたときに爆発しがち// Մಡੑͷ͍ίʔυ
実装のtips③style© - BASE, Inc.• ⼀時的な状態に依存するスタイルはdata-*やaria-*と属性セレクタの組み合わせで当てる• 例) ローディング中、選択中か、など.foo {&[aria-selected="true"] { color: $green; }&[aria-selected="false"] { color: $gray; }}
実装のtips④scoped-slot© - BASE, Inc.コンポーネントの内部状態を⼀元管理するコンポーネントが欲しくなる場合にはscoped-slotを利⽤する例)• テーブルのヘッダとリストの結びつき管理• モーダルのトリガとなるコンポーネントとモーダルのコンテンツ
実装のtips④scoped-slot© - BASE, Inc.モーダルのサンプル දࣔ͢Δ ίϯςϯπ
まとめ© - BASE, Inc.• コンポーネントでいちばん⼤事なのは責務の分離• 特にContainerとPresentationalなコンポーネントの役割わけをきっちり守るのは重要• いつでもコード(特にUI側)を捨てられるように。• Storybookには広い利⽤者に向けたおもてなしの⼼が必要• ちょっとした⾶び道具的な修正でコンポーネントの前提が壊れても泣かない
© - BASE, Inc.ありがとうございました