デザインを高速に実現するReactとCSS in JS
React Componentが高速なプロトタイピングを実現する
高速なプロトタイピングにはまず、スピーディーにUIを構築する手法が必要となります。これはReactの得意分野です。
Reactの特徴の1つに、UIにおけるコンポーネント志向が挙げられます。再利用可能な小さいUI部品を組み合わせて全体を構築するアプローチです。Reactにおける基本単位はReact Componentであり、この小さなReact Componentを組み合わせてページを構成します。
さらにReactではデザインもReact Componentの中に閉じ込めることが可能です。React ComponentにCSS相当の装飾やレイアウトを記述しておくことで、React Componentを呼び出すだけでデザイン適用済みのUI部品を表示できます。CSSをJavaScriptのUI部品の中に閉じ込めることからCSS in JSとも呼ばれています。
例えば、AppBarというReact Componentを利用する例を見てみましょう。下記の通り記述し呼び出すと、AppBarが表示されます。
<AppBar title="Title" /> //AppBarというReact Componentを利用する例
このようなアプローチで、CSSに詳しくない開発者でもある程度のUIのアプリを作ることができます。K5 PlaygroundではMaterial-UIというCSS in JSを採用したUI部品を用意していて、呼び出すだけでデザイン済みの部品が表示されるようになっています。
レイアウトの部品化
CSS in JSの応用の1つが、レイアウトの部品化です。
これまでのレイアウトは地道にCSSを記述する方法が主で、Bootstrapなどのライブラリを使うにしても、フレームワークの作法に従ってDOMの階層構造を作成して、指定されたクラス名を記述する…など、手数が多くなりがちでした。
レイアウトの部品化はこれを一変させます。レイアウト用のReact Componentに対して、表示させたいReact Componentを「代入」するだけで適切にレイアウトを行い配置してくれます。HTMLやCSSの知識を一切持たなくてもレイアウトを実現できます。
具体例を挙げて説明しましょう。Webにおける代表的なレイアウトにHoly Grailレイアウト(聖杯レイアウト)があります。聖杯レイアウトは、ヘッダー、フッター、左右のメニュー、中央コンテンツの5要素から構成されるレイアウトです。
K5 Playgroundでは聖杯レイアウトのための <BaseLayout>
というReact Componentを用意しています。これを使えば聖杯レイアウトは次のように実現できます。 <Header />
などはReact Componentとします。
<BaseLayout top={<Header />} //ヘッダーのReact Component left={<Menu />} //左メニューのReact Component center={<Content />} //メインコンテンツのReact Component right={<Navi />} //右メニューのReact Component bottom={<Footer />} //フッターのReact Component />
また、次のように右メニューやフッターを省略すれば、聖杯レイアウトから右メニューとフッターを引いたレイアウトが実現できます。
<BaseLayout top={<Header />} //ヘッダー left={<Menu />} //左メニュー center={<Content />} //メインコンテンツ />
縦並びや横並びのレイアウトを実現するレイアウト部品VerticalLayoutやHorizontalLayoutも用意しています。例えば、TextLabelというReact Componentを縦に並べたい場合は、以下のようになります。
<VerticalLayout> <TextLabel /> <TextLabel /> : </VerticalLayout>
K5 Playgroundで独自の画面に変更したい場合は、これらのレイアウトコンポーネントを使うのが簡単です。聖杯レイアウトと、縦並び・横並びレイアウトを組み合わせれば大抵のレイアウトは実現できます。もちろん通常のCSSを使うことも可能ですが、まずはレイアウト部品にUI部品を流し込んで複雑なUIをスピーディーに作ってみましょう。
具体的には、以下の流れでUIを構築できます。
-
まず、
frontend
配下でnpm start
を実行します。 -
すると
webpack-dev-server
という開発用のサーバーが起動して、ブラウザにアプリが表示されます。 -
次に、
webpack-dev-server
が起動した状態でfrontend/app/components
配下に格納されているReact Componentを修正します。 -
その後、修正が
webpack-dev-server
に検知されてブラウザが自動的にリロードされて、新たなUIが表示されます。
文法ミスなど、React Componentに対する修正内容が誤っている場合は、エラーとなって即座に分かります。このように npm start
で開発用サーバーを起動して、表示を確認しながら少しずつ修正すると効率的です。
デザインに関する作業には際限がなく、後から改善も可能なため、細部にとらわれすぎず大部がある程度できたら次に進むスタイルでスピーディーにUIを作っていきましょう。
BFFでモックAPIを作ろう
UIに表示させるデータはどうすればよいでしょうか。バックエンドを全て取りそろえてからフロントエンドを開発する方法だと、プロトタイピングで求められるスピードに到達できないでしょう。
これを解決する手法の1つがモックAPIです。モック(mock)は「模造的な」という意味で、モックAPIはRDBMS等のバックエンドのサービスは呼び出さずに、HTTPリクエストに対して固定的なダミーのJSONを送受信します。重要な部分以外はモックAPIを使って、スピーディーにフロントエンドを形にして価値を評価しましょう。
K5 PlaygroundでモックAPIを作成する
K5 PlaygroundでモックAPIを作成するのは簡単です。既存のAPIロジックと空のAPIロジック(Empty Logic)を交換して、モックデータを書くだけです。具体的な手順を見てみましょう。
- まず中央にあるAPIロジックを、×をクリックして削除します。
- 次に右メニューのCustomにある「Empty Logic」を中央にドラッグ&ドロップします。
-
最後にEmpty Logicの
next()
メソッドの引数にモックオブジェクトを渡します。
ここでは静的なオブジェクトを next()
に渡しただけですが、条件に応じて動的にオブジェクトを生成すると、より高度なモックAPIが作れます。HTTPリクエストの内容に応じてレスポンスを分岐させたい場合は、HTTPリクエストのオブジェクトreq
を参照します。例えばURLのクエリ文字列ならば、 req.query.KEY_NAME
で参照できます。HTTPリクエストオブジェクト req
の詳細は、K5 PlaygroundのBFFでも利用しているNode.jsのWebアプリケーションフレームワークexpressのドキュメントを参照してください。
APIの設計を向上させるためのモックAPI
モックAPIのもう1つの利点は、動作する設計であることです。
API単体ではよさそうな設計であっても、フロントエンドの設計を考えるとの設計を考えると「1ページあたりのトランザクション数が多い」「レスポンスのJSONの構造や型が扱いづらい」といった不満が出てくることは珍しくありません。早期にモックAPIを作成することで、フロントエンドの動作を考慮して改善点を洗い出し、APIの設計を修正できます。モックAPIの段階でさまざまなAPI利用者の声を聞いて質を高めることが大切です。
またBFFがないアーキテクチャの場合、技術的にはモックAPIそのものを容易に修正できても、本物のバックエンドは組織やコストの面から修正が困難なケースが多々あります。K5 PlaygroundにはBFFがあるため、バックエンドと距離を置いてフロントエンドと親和性の高いAPIをBFFで実現できます。
FluxでモックAction CreatorやモックStoreを作成しよう(1)
開発のスピードアップをさらに追求したい場合、データの生成点をモックAPIではなく、フロントエンドの内部に移動させることもできます。APIを呼び出した後にダミーデータを生成するのではなく、APIを呼び出さずにフロントエンド内部でダミーのデータを生成するアプローチです。
一般的にSingle Page Application(SPA)では、バックエンドから取得したデータなど、フロントエンドの「状態(state)」を何らかの方法で管理して、UIに表示させます。ReactとFluxの場合は、「Store」というFluxのコンポーネントがこのstateを管理する役割を担います。stateをさまざまな方法でモック化し、フロントエンド内でダミーデータを生成することでアプリを高速に開発できます。
まずは、state管理にとって重要なFluxアーキテクチャを見ていきましょう。
Fluxアーキテクチャ概説
FluxはFacebookが提唱したフロントエンドのためのアーキテクチャです。フロントエンドの「状態」の管理に重きを置いています。Reactと親和性が高いだけでなく、JavaScrip以外の言語やフレームワークのフロントエンドに適用できるアーキテクチャです。
FluxアーキテクチャはView、Action/Action Creator、Dispatcher、Storeから構成されます。
Fluxの 構成要素 |
概要 |
K5 Playgroundでの ファイル格納場所 |
---|---|---|
View | React ComponentやContainer |
frontend/app/components |
ActionCreator | イベントを処理して結果をActionという形式にしてDispatcherに渡すメソッド |
frontend/app/actions |
Action | イベントの処理結果(APIレスポンスなど) |
frontend/app/actions |
Dispatcher | ActionをStoreに送信するハブ機能 |
frontend/app/dispatcher |
Store | 状態を管理する非永続的なデータストア |
frontend/app/store |
FluxアーキテクチャをFacebookのFluxライブラリを用いたサンプルコードを通して説明します。
まずはViewであるReact Componentを起点とします。ユーザーがReact Componentに対してマウスクリックなどのイベントを発生させるとイベントハンドラが呼ばれます。ButtonというReact Componentを利用する例です。クリックされると同じReact Component内に定義された handleClick
メソッドが呼ばれます。
<Button onClick={this.handleClick} title="Update"/>
イベントハンドラは、Action Creatorを呼び出します。
handleClick = () => ActionCreator.updateOrder();
Action Creatorの基本的な書式です。イベントに対する処理を行い、結果をActionというオブジェクトにして、Dispatcherの dispatch()
メソッドに渡します。イベントに対する処理の例は、API呼び出しやDB呼び出し、業務ロジックの実行などバックエンドが関わるものからフロントエンド内部に閉じたものまで多岐に渡ります。 dispatch()
に渡されたActionはその後、Storeに送信されます。
//複数のAction Creatorの集合 const ActionCreators = { //Action Creator updateOrder: () => { //1. APIを呼び出す api.get(url).then(body => { //2. APIのレスポンスからActionを生成する const action = { type: 'order/update' //Acitonの種別を表す識別子 data: body //イベントの結果 } //3. Dispatcher経由でStoreにActionを送信する Dispatcher.dispatch(action); }) }, }
上記は説明的に記述しましたが、より簡潔にも記述できます。
updateOrder: () => api.get(url).then(body => Dispatcher.dispatch({type: 'order/update', data: body}))
StoreではActionCreatorがDispatcherに渡したActionを受け取り、新たな状態(state)を作り出します。StoreはgetInitialState()
と reduce() の2種類のメソッドを持ちます。 getInitialState()
の戻り値にはStoreの初期値を設定します。 reduce()
では、現在の state
と受け取った action
から新たな state
を作成します。
import { ReduceStore } from 'flux/utils'; //ReduceStoreを使うのが基本です。 import AppDispatcher from '../dispatcher/AppDispatcher'; class OrderStore extends ReduceStore { //Storeの初期のstateを返す必須のメソッドです。 getInitialState() { return {}; //初期のstateを定義します。 } //現在のstateとactionを受け取って新たなstateを返す必須のメソッドです。 reduce(state, action) { switch (action.type) { // actionのtypeによってStoreのstateの作り方を分岐させます。 case 'order/update': { //注文更新の例 return action.data; // 受け取ったactionをそのまま新たなstateとする例です。 } case 'order/delete': { //注文削除の例 return {}; // 空のオブジェクトを新たなstateをとする例です。 } default: { return state; } } } } export default new OrderStore(AppDispatcher);
Storeの内容が更新されると、それを自動的にContainerが受け取ります。Containerは次のとおり、Storeからデータを受け取るだけの特殊なReact Componentです。ContainerがStoreの内容を受け取ったら、配下のReact Componentに渡します。
import React, { Component } from 'react'; import { render } from 'react-dom'; import { Container } from 'flux/utils'; //サンプルの(Reduce)Storeです。 import OrderStore from './stores/OrderStore'; import RecommendationStore from './stores/RecommendationStore'; class OrderContainer extends Component { static getStores() { return [OrderStore, RecommendStore]; //stateを取得したいReduceStoreを定義します。 } static calculateState() { return { //取得したstateはここで定義した形式で参照できます。 order: OrderStore.getState(), recommendation: RecommendationStore.getState(), }; } render() { //Storeから取得したstateを配下のReact Componentに渡します。 <OrderPage order={this.state.order} recommendation={this.state.recommendation} /> } } export default Container.create(OrderContainer);
Viewからスタートして、ActionCreator、Dispatcher、Storeを経由して再びViewに戻ってきました。本ページ冒頭の図のように、データを一方通行で流して単純化するのが特徴です。
次のページから、Fluxアーキテクチャでモックを実現する方法を2つ紹介します。
FluxでモックAction CreatorやモックStoreを作成しよう(2)
方法1. モックAction Creator
Action Creatorで、ダミーのActionを生成して dispatch()
に渡します。実際はAPIやDBをコールしませんが、コールしたと仮定して、結果をActionという形式で dispatch()
に渡します。
const ActionCreators = { getArticle: () => Dispatcher.dispatch({type:'type1', data: {id: 1, text: 'How to use...'}}), : }
モックAPIであっても物理的なAPIが存在する場合、APIの設計に多少の難があったとしてもフロントエンド側で無理をしてしまいがちです。モックAPIより前の段階でモックAction Creatorを作って試行錯誤することで、フロントエンド内部の設計を洗練させることができます。
モックAction Creatorによるネットワーク透過性
試作段階でデモを行う際に、ネットワークの影響を排してアプリの振る舞いを固定したい場合はないでしょうか。次はモックAction Creatorを使ってあらかじめ取得したツイートを利用する例です。
import mockTweet from `../mock/tweet.json`; //あらかじめ取得したTweet const ActionCreators = { fetchTweet:() => Dispatcher.dispatch({type:'foo', data: mockTweet}), : }
ネットワークの状態に応じて、APIから取得するかモックデータを使うか、切り分けることもできます。以下はAPIがエラーの時だけモックデータを使用する例です。
fetchTweet:() => api.get(url) .then(res => Dispatcher.dispatch({type:'foo', data: body})) //APIで取得したTweet .catch(Dispatcher.dispatch({type:'foo', data: mockTweet})) //事前に取得したTweet
あらかじめネットワーク状態を設定して処理を切り替えることもできます。
fetchTweet:() => isReachable ? api.get(url).then(body => Dispatcher.dispatch({type:'foo', data: body})) // APIで取得したTweet : dispatch({type:'foo', data: mockTweet}) //事前に取得したTweet
方法2. モックStore
Viewに表示させるデータを初めからStoreに格納しておく方法です。具体的にはStoreの getInitialState()
の戻り値にモックデータを設定します。
class Store extends ReduceStore { getInitialState() { return [{id: 1, text:''}, {id:2, text: ''}, ...]; } : }
モックStoreは、柔軟性はないもののこれまで紹介した中では最も高速にViewを構築できる手法と言えるでしょう。
ReactとFluxを使うとアプリのViewから状態(Store)や操作(Action Creator)をきれいに分離できます。フロントエンドのあらゆる要素をコンポーネント化し、柔軟にモックと本物を切り替えられるため、プロトタイプから本番まで連続的に開発を進めることができます。
どのモック手法を使えばよいか
これまでBFFやSPAで、さまざまな要素をモック化してフロントエンドをスピーディーに形作っていく手法を紹介してきました。実際の開発ではモック化する手法をどう使い分ければよいでしょうか。次の順序で重要度の高い箇所から移行させるのがおすすめです。
- モックStoreによってViewを構築する。Viewと状態を分離して、Storeの粒度を改善する。
- 本番のStoreとモックAction Creatorに切り替える。操作を洗い出し、粒度を改善する。
- 本番のAction CreatorとモックAPIに切り替える。APIの設計を改善する。
- モックAPIを本番のAPIに切り替える。
ユーザーとの接点から先に仕上げていって感触をつかみ、うまくいきそうだったらバックエンドに移っていく「フロントエンド・ファースト」な開発スタイルです。
とにかく大切なのは手を止めずに大ざっぱでもいいから先に進むことです。ReactとFluxという構造化されたフロントエンドを手にしたわれわれは、あらゆる要素をモック化でき、粒度を適正化しながら推移的に改善できます。モックをうまく活用して、楽器を演奏するようにテンポよく開発していきましょう。
モックデータの作成にはMarak/faker.jsのようなダミーデータを作成するツールを用いて、プログラマブルに作れるようにしておくことも大切です。
次のアイデアを早期に試す
アプリ開発においては、「よいと思ったアイデアを形にしてみたら、イメージと違っていた」といったこともしばしばあるでしょう。こういった場合はひとまずそのアイデアは横に置いて、次のアイデアに取り組むのが手です。新たな取り組みには失敗がつきものなので、早期に失敗を検知する「Fail Fast」の考え方も大切です。
K5 Playgroundならば「Fail Fast」が容易に実現できます。通常のアプリ開発では慣れている人でもそれなりに手間や時間がかかるので、微妙だと気づいても心理的に失敗と認められないものです。一方、K5 Playgroundで手間をかけずに短時間で作ったアプリであれば「Fail Fast」する心理的なハードルは低いはずです。一筋縄ではいかないアクションゲームを何度もリトライするように、繰り返し新たなアイデアに挑戦してみましょう。
ただし「Fail Fast」は「Move Fast」と対でなければなりません。
Y Combinatorの創業者ポール・グレアムは、著書『ハッカーと画家 コンピュータ時代の創造者たち』に収録されている『メイド・イン・USA』という随筆の中で、
"コードは、ピラミッドみたいに、慎重な計画をしてから苦労して組み立てていくものじゃない。一気に集中して素早く手を動かしながら、常に気を変えていく、木炭スケッチみたいなものだ。"
あるいは、
"ゆっくりと念入りに仕事をしていると、出来上がるものは当初のアイディアを精密に実現したものになるだろう。ただしそのアイディアは間違っているだろうけどね。遅く念入りな仕事は早すぎる最適化だ。むしろプロトタイプを素早く作り上げて、それによって新しいアイデアを得てゆくほうがよい。"
と、ソフトウェア開発におけるスピードの重要性を説いています。失敗を恐れるのは人間の常ですが、今回紹介したようなテクニックを身につけて、木炭スケッチを描くように開発し、「Fail Fast」と「Move Fast」を繰り返して力をつけながら、よいアイデアが降って来るチャンスを待ちましょう。