自己紹介
今回がQiita初投稿となります。
普段はメーカーでC++を使った画像処理系のアプリ開発に携わってます。
もともとWEBには一切触れた事がなかったのですが、
世の中でReactやVueといったフレームワークが話題になった時から
徐々にWEB業界に興味を惹かれてきました。
そして本格的に勉強してみようと思い立ち、今回のアプリ制作に至りました。
サービス紹介
Mushiquiという虫食いクイズアプリを作成しました。

きっかけは社内の暗記試験で、解答を含む全文から手っ取り早く虫食い文章を作成するツールが欲しいなと思った経験からでした。
特定のタグで文字を囲うことで、手軽に虫食いクイズが作成できるようになります。
ブラウザ/モバイルから利用が可能で、PWA対応しているため、ホームに追加して利用が可能です。
制作期間
技術の理解を含めて一年半程になります。
WEBの仕組みからの理解で、一進一退の日々でした。
途中段階の出来を見てショックを受け、実際何度か投げだしましたが(汗)、
それでも地道に実装を続け、なんとか当初想定していた機能が一区切りついたため記事にする事に踏み切りました。
代表的な利用技術
フロントエンド
- react
- redux
- redux-first-router
- algoliasearch
- draft-js
- styled-components
- storybook
- react-responsive
redux-first-routerはルーティングに使用。
draft-jsはテキストエディタに使用。
バックエンド
- Firebase
- Firestore
- Cloud Function
- Authentication
- Hosting
- Storage
- Algolia
システム構成
システム構成は以下のようになっています。
今回、バックエンドにはFirebaseを用いており、データベースにはCloud Firestore、認証にはAuthenticationを使っています。
またクイズの全文検索を実現するために、Algoliaを用いており、AlgoliaのインデックスとFirestoreのデータはCloud Functionを使って同期しています。
苦労した点
学ぶ量の多さ
WEBページを作ること自体が初めての自分にとって、
いきなりReactで物を作るというのは情報量との戦いでした。
QiitaやMediumなどを見れば、Reactの使い方はなんとなくわかるのですが、
自分で考えて使えるようになるまでは、
htmlやcssをはじめjsxなどの根底知識が必要で相当の時間を要しました。
そんな中で最後まで心の頼りだったのが、公式ドキュメントでした。
量がそれなりにあって、全部読むには相当時間がかかりますが、
最近では日本語ページも充実しています。
初期のチュートリアルはもちろんなのですが、アプリを作り始めてから暫くして構造が複雑になってきて悩み始めた時にも、render propsやHOCなどのテクニックが記載されているADVANCED GUIDESは個人的にとても助かりました。
コンポーネントの責務分割
サンプルを見よう見まねで作ったアプリの状態から色々な機能が盛り込まれていくと、
似たようなデザインのコンポーネントが散在し、ボタンの修正ひとつで複数のソースを修正しなくてはいけない非効率な状況に陥りました。
そうしたときに、デザインの変更を局所的に留める仕組みを考えておくことは重要で、自分の場合はAtomic Designを用いることでこの手間を大幅に改善することができました。
Atomic Designについて:
http://atomicdesign.bradfrost.com/chapter-2/
Mushiquiでは以下のようにcomponents下のフォルダ構成と責務を決めています。
components
|-atoms
|-moleculeus
|-organisms
|-templates
|-pages

全体のレイアウトや色合いを変えたいときはtemplates、
ボタンの振る舞いやデザインを変えたいときはatomsといったように
変更する目的に応じて、変更する場所を決めておけば
仮に第三者から見てもわかりやすいのではないかと思います。
コンポーネントの責務分割(2)
上記の責務の分割をしたうえでも、
WEBとモバイルは同じコンポーネントでもレイアウトが大きく変化する場合があるため、必要に応じて表示部分のみ責務を分割する必要がありました。
(特にtemplatesやorganisms層で発生)
今回、react-responsiveを用いてモバイル用PresenterとWEB用Presenterに振り分けています。
Component
|-Container.js
|-WebPresenter.js
|-MobilePresenter.js
|-index.js
|-index.stories.js
|-style.js
- Container.js・・・このコンポーネントにおけるStateを管理するClass Component
- WebPresenter.js・・・WEB向けの表示を行うFunctional Component
- MobilePresenter.js・・・Mobile向けの表示を行うFunctional Component
- index.js・・・ContinerとPresenterをrender propを使い接続。ウィンドウサイズに応じた振り分けも実施。
import React from 'react';
import Responsive from 'react-responsive';
import WebPresenter from './WebPresenter';
import MobilePresenter from './MobilePresenter';
import Container from './Container';
export const Mobile = props => <Responsive {...props} maxWidth={767} />;
export const Default = props => <Responsive {...props} minWidth={768} />;
const FavoriteList = (props) => {
return <Container
{...props}
render={(containerProps)=><React.Fragment>
<Default>
<WebPresenter {...containerProps} />
</Default>
<Mobile>
<MobilePresenter {...containerProps} />
</Mobile>
</React.Fragment>}
/>;
};
export default FavoriteList;
Algoliaとの連携を考慮したFirestoreのデータ設計
クイズを全文検索可能にするために、
Firestoreのデータの一部をAlgoliaのインデックスに登録しています。
Firestoreのクイズ更新をCloud Functionでトリガし
更新の反映をAlgoliaのインデックスに対しても行います。
こうしたときに複雑になるのが、Firestoreのデータの持ち方でした。
検索にはクイズ名称やタグ、作成者などのメタ情報をインプットするために
これらの情報は、すべてAlgoliaのIndex内に持つ必要がありました。
一方で検索にはクイズ本体の情報は不要のため、
クイズ本体の変更はトリガしないようなFirestoreのデータ設計が必要となりました。
結果として、検索時に不要なクイズ本体はサブコレクションとして、他のメタ情報と分離することで上記を実現することができました。

(図中のrecipesはクイズ本体(quizzes)とメタ情報を集約する概念)
最後に
長くなりましたが、ここまで読んでいただきありがとうございます。
これから始めるという方や、Reactのアプリ制作で悩まれている方の少しでも参考になれば幸いです。
今後はHooksやContextなど今回のアプリでは取り入れられなかった要素を学習しつつ、随時アプリの更新や、今回書ききれなかった内容についても書いていきたいと思います。