2004年から続くブログサービス「アメブロ」が2016年9月にシステムをリニューアル。本連載では、そこで取り入れた主要な技術や、その効果を紹介していく。今回は、ReactやRedux、Atomic Designなど、フロントエンドを構成する技術について。
2004年から続くブログサービスである「アメブロ」は、2016年9月にシステムをリニューアルしました。本連載「大規模ブログサイト表示速度改善 大解剖」では、そこで取り入れた主要な技術や、その効果を紹介していきます。初回である前回の「アメブロでReactやIsomorphic Web Applicationを採用した理由――その成果と構成技術」では、Isomorphic Web Applicationの利点や、その作り方について解説しました。
第2回となる今回は、ReactやRedux、Atomic Designなど、フロントエンドを構成する技術についてお伝えします。
アメブロシステムのリニューアルでは、ユーザーインタフェースライブラリとしてReactを採用しましたが、他にもVue.jsやAngularJS、Riot.jsなどと比較しました。
「Isomorphic Web Applicationを作る上で必要な機能がそろっているか」「実績があるか」「コミュニティーが十分に育っているか」といった視点で見ていった結果、プロジェクトがスタートした当時(2015年)ではReactが一番信頼できるものでした。また、Facebook社で開発されており実際にProduction環境でも使われている安心感もありました。
Webサイトでは一般的に、表示内容をページ単位で管理していました。そこではページごとにHTMLが用意され、付随してスタイルシートやJavaScriptが作られました。その構成でシングルページアプリケーション(SPA)を作ろうとすると、表示パーツの再利用がしにくく、変更時の副作用(他の表示への影響)が分かりにくいこともありました。
それらを解決するため、ページではなくページを構成する部分を独立して管理する、「コンポーネント指向」がWebサイトの開発に持ち込まれました。今回われわれが選択したReactだけではなく、近年作られたViewライブラリにはコンポーネント指向で設計できるように作られたものがよく見られます。
コンポーネント指向では、各コンポーネントで「状態・処理・表示する内容」を定義できることが要求されます。コンポーネント指向は今後、Webの開発において、より採用が増えていくことが想定されます。Webフロントエンドのライブラリは流行の変化が激しい分野ではありますが、コンポーネント指向で記述しておけば、ライブラリ移行の際にも変更時の影響範囲が分かりやすいため、比較的容易に行えるでしょう。
Reactでは、コンポーネントをJavaScriptで記述します。最も基本的なReactコンポーネントはJavaScript関数であり、誰が見ても「何をしているか」を簡単に理解できます。今後、新たなコンポーネント記述法が普及するかもしれませんが、現時点では「通常のJavaScriptでコンポーネントが管理できる」ことはメリットの1つであるといえます。
const Hello = () => ( <p>Hello</p> );
Reactでは、状態を「props」「state」、処理を「メソッド」、表示する内容を「主にJSX」で表現します。Reactコンポーネントは、それ自体を関数と同じように扱えるため、同じ引数(props)を与えれば、同じ表示結果(HTML)が保証されるピュアなコンポーネントとして作ることができます。そのように作っておくことは、デバッグやテストのしやすさのために重要です。
コンポーネントの状態は、リスト1のように「props」で管理します。一時的な状態を「state」として管理することもできますが、「ピュアなコンポーネント」としての見通しが、やや悪くなります。また、後述するReduxでは状態を1つのJavaScriptオブジェクトにまとめるため、各コンポーネントでstateを使わなくてよい設計にすることも可能です。
class Message extends React.Component { static propTypes = { message: React.PropTypes.string.isRequired, }; render() { return ( <p>{this.props.message}</p> ); } }
リスト2はピュアなコンポーネントではない例です。コンポーネント内で与えられた値をランダムにしてしまっているため、表示内容が一定でなくなってしまいます。
class Message extends React.Component { static propTypes = { messages: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, }; render() { return ( <p>{_.sample(this.props.messages)}</p> ); } }
アメブロのシステムリニューアルの際には、Isomorphic Web Applicationを作る上で、社内実績がある「Fluxible」「Redux」のどちらを使うかで悩みました。最終的には、コミュニティーの成長性や今後の可能性を考慮して、Reduxを選択しました。Redux独自のルールに慣れるまでにしばらく期間が必要でしたが、デバッグのしやすさ、state管理のしやすさの点で大規模サービスにも耐えられるようになっており、結果としては導入してよかったと思っています。
ReduxはFluxアーキテクチャに影響を受けています。Fluxは、状態管理の流れを「Action」「Dispatcher」「Store」「View」といった名称を使って、定義しました。状態に変更が起こった際には必ず同一方向の流れで伝播するため、一貫性があり、デバッグ・テストがしやすいコードを書くことができます。
ReduxのFluxとの大きな違いは「Dispetcher」を独立でなくした点と、単一Storeにした点です。
Reduxでは、ほとんどの部分をピュアな関数のみで組み立てられます。そうすることで、そのコードの結果がより予測しやすいものとなりました。何か変更がある際には必ずActionを起点とするので、「今アプリケーションで何が起こったか」を把握するのが簡単です。ActionはただのJavaScriptオブジェクトであるため、コードを読むことなくまさに“見たまま”で何が起こったのかが分かります。「Reducer」に整形処理を書き、「Store」にその時々の状態(State)をためていきます。
これらの仕組みを使うことで、ピュアな関数の組み合わせによる副作用の少ないプログラミングで、「状態と処理を分けること」ができるようになりました。
Actionは、リスト3のようにピュアな関数「Action Creator」によって作られます。与えられた引数に対して、常に一定のオブジェクトを返却します。Actionの記法についてはFlux Standard Actionを参考にするとよいでしょう。
function addTodo(text) { return { type: 'ADD_TODO', text } }
リスト4はReducerの例です。必要な整形処理を行い、新たなstateとして返します。こちらもActionと同様に与えられた引数に対して、常に一定のオブジェクトを返却します。
function todoApp(state = initialState, action) { switch (action.type) { case ADD_TODO: return { ...state, todos: [ ...state.todos, { text: action.text, completed: false } ] }; default: return state; } }
Reduxは単一Storeであるため、そのStoreさえあれば状態を再現できます。ピュアなコンポーネントであるReactと組み合わせて作れば、直前直後に移動するundo/redo機能だけでなく、特定の表示状態までどこにでも戻すことができます。
また、単一Storeであることで、サーバサイドで作ったRedux Storeをそのままクライアントサイドに受け渡すことも簡単です。アプリケーションの規模が大きくなった際には、ActionやReducerを小分けにして管理します。
開発中にStoreの状態を確認するにはredux-devtoolsがおすすめです。
Copyright © ITmedia, Inc. All Rights Reserved.