www.carecom.de の構築

React、TypeScript、Redux、ASP.NET Core を土台とするユニバーサルなシングルページアプリケーション carecom.de の背後にある技術と実装について、ささやかな解説の旅。

Harald Mühlhoff 読了 1 分

www.carecom.de は、Redux、React-Router、TypeScript、Bootstrap と ASP.NET Core バックエンドを用いた React によるユニバーサル(アイソモーフィック)シングルページアプリケーション(SPA)です。

本サイトは優れたプロジェクト Microsoft ASP.NET Core JavaScript Services をベースにしています。拡張の一部は GitHub の素晴らしいコミュニティから取り入れたもので、その他は自前で実装しました。

  • マルチテナント対応 — このすべての恩恵を顧客にも気軽に提供できるように
  • ローカライゼーション(クライアント側・サーバー側ともに)
  • メタタグとタイトル
  • Google Analytics コンポーネントを追加
  • Google Ads コンポーネントを追加
  • クッキー警告コンポーネントを追加
  • 税込/税抜価格切り替えコンポーネントを追加
  • 基本的な認証処理を追加
  • モーダルウィンドウコンポーネントを追加
  • お問い合わせフォームコンポーネントを追加
  • コード整形コンポーネントを追加
  • スムーズスクロール/ハッシュタグへのスクロールコンポーネントを追加
  • Sitemap.xml
  • サーバーとクライアント間の「グローバル状態」の受け渡し
  • クッキー処理の改善
  • ネストされた redux リデューサー
  • ……

この記事は時間をかけて拡充し、本アプリケーションの設計についてさらに多くの側面を取り上げていく予定です。土台となる技術そのものの解説は、本記事の範囲を超えます。

今のところ、これはこれから書かれることの萌芽にすぎません。

気に入っているのは:

  • Redux が再利用可能なコンポーネント — あるいは次のような「その場限り」のコンポーネント — をいかに容易に作れるか:
    let PhotographySlider = (props) =>
            <Slider id="PhotographySlider" baseUrl="/tenants/carecom/images/photography/slides/" fromNr={1} toNr={42} />
  • オブジェクトの分割代入のような ES6 のエレガンスさ:
    es6 destructuring objects
  • ……

本サイトの React で書かれた中核コンポーネント Layout の一部(JSX を使用、{} 内のコードは TypeScript)を見てみましょう:

        return <div className="layoutContainer">
            <DocumentMeta {...getMeta()} />

            <NavMenu userId={this.props.userId} isLoggedIn={this.props.isLoggedIn} onLogout={this.props.onLogout} />
            {this.props.slider}

            <main className='container mainLayoutContainer'>
                {
                    breadCrumb &&
                    <div className='row hidden-xs hidden-sm'>
                        <div className='col-md-6 breadCrumb'>{breadCrumb}</div>
                    </div>
                }

                {
                    title &&
                    <div className='row'>
                        <header className={`col-md - ${(side ? 0 : 3) + (translations ? 6 : 9)} col-xs - ${ translations ? 9 : 12 } `}>
                            <h1>{title}</h1>
                        </header>

                        {
                            translations &&
                            <div className='col-xs-3 translations'>
                                <span className='glyphicon glyphicon-flag'></span>&nbsp;{translations}
                            </div>
                        }
                    </div>
                }

                <div className='row'>
                    <div className={`col- md - ${side ? 9 : 12 }`} role='main'>{ this.props.body }</div>

                    {
                        side &&
                        <aside className='col-md-3'>{ side }</aside>
                    }
                </div>

            </main>

            <footer>
                <BottomMenu />
                <Footer />
            </footer>
        </div>

DocumentMetaReact Document Meta 由来)は、titledescriptionmeta タグといった情報を、この Layout を利用するコンポーネントに紐付ける役割を担います。getMeta() はヘルパーモジュールに由来し、現在のルートに対応するメタデータを取得します。

パンくず、タイトル、翻訳(当該ルートの他言語版へのリンク)は、現在のルートがそれらを提供する場合にのみレンダリングされます。

export default (
    <Route path='/' component={Layout}>
        { /* de */ }
        <IndexRoute components={EnhHomeComp} />

        <Route path="de">
            <IndexRoute components={EnhHomeComp} />

            <Route path='consulting'>
                <IndexRoute components={{ body: Consulting, side: ConsultingQuotation }} />
                <Route path='informatiker' components={{ body: ComputerScientist, side: SideHaraldMuehlhoff }} />
                <Route path='referenzen' components={{ body: ConsultingReferences }} />
                <Route path='remote-support' components={{ body: RemoteSupport, side: SideHaraldMuehlhoff }} />
            </Route>

            <Route path='distribution'>
                <IndexRoute components={{ body: Distribution, side: DistributionQuotation }} />
                <Route path='referenzen' components={{ body: DistributionReferences }} />
                <Route path='selbstbedienung' components={{ body: SelfService }} />
            </Route>

例えば、すべての Routeside コンポーネントを持っているわけではありません。これは適用される Bootstrap のスタイル(className={`col-md-${side ? 9 : 12}`})に影響します。

エラーが発生しました。 再読み込み 🗙

サーバーへ再接続しています …

再接続に失敗しました — 秒後に再試行します。

再接続に失敗しました。
もう一度お試しいただくか、ページを再読み込みしてください。

サーバーがセッションを一時停止しました。

セッションを再開できませんでした。
もう一度お試しいただくか、ページを再読み込みしてください。