LoginSignup
5

More than 5 years have passed since last update.

Dockerで簡易版Aidemyをつくってみよう (その2) ~React.jsによるフロントエンド~

Last updated at Posted at 2018-12-11

概要

Aidemy アドベントカレンダー12日目担当の @km42428 です。
今回は、Dockerで簡易版Aidemyをつくってみようシリーズのその2です。
前回の記事では全体の構成をお伝えしましたが、今回からは実際に開発したいと思います。

今回の目標

今回は、React によるフロントエンドを作成し、docker-composeコマンドで一発で起動するのが目標です。
フロントエンドはユーザと直接やりとりする部分で、ユーザの入力を受けたり、その情報をバックエンドへ送信、バックエンドから情報を取得して表示する役割があります。Reactについては公式サイトをご覧ください。
作成するのは、いわゆるWebページです。ソースは以下に公開しています。
https://github.com/km42428/aidemy-simple-pack/tree/frontend

今回の記事は frontend ブランチで動作しています。
masterブランチは適宜更新しているので、この記事と同じ動作をさせたい場合は、
以下のコマンドを入力してください。
自分の作業ブランチは適宜書き換えてください。

# クローン
$ git clone https://github.com/km42428/aidemy-simple-pack.git 

# リモートのfrontendブランチにチェックアウト
$ git fetch && git checkout origin/frontend

# ローカルの自分の作業ブランチにチェックアウト
$ git checkout -b {自分の作業ブランチ} 

フロントエンドの機能としては、エディタ部分にコードが入力できるようにし、
送信ボタンを押すと、イベントが発火して今回はalertが表示されるようにします。
ただし、cssについてはあまり手を加えないことにします。
理由は、僕がデザインセンスが皆無なことと、
今回の趣旨はReactでの簡単なデータの扱い方を見るのが目的だからです。

ページ構成 (ルーティング)

今回は以下のようなページ構成を考えます。
プレビューが小さくて見えない場合は、画像をクリックすれば大きく見えます。

ルート ページ プレビュー
/ 問題一覧 スクリーンショット 2018-11-18 21.46.33.png
/exercises/:exerciseId 演習画面 スクリーンショット 2018-11-18 21.47.02.png

ちなみに、 /exercises/:exerciseId のRUN押下時は下のようになります。
スクリーンショット 2018-12-11 15.03.51.png

事前準備

 フロントエンドの挙動をローカルで確認するには、
nodenpm を事前にインストールする必要があります。
インストール手順については
以下の記事を参照してください。
※ 今回の最終目的はDockerを使って環境開発不要で起動することです。
なので、環境開発なんてしたくないやいっ!という方は、
ソースをcloneした上で、Dockerによる操作からお読みください。

Ubuntuの場合

Macの場合

Windowsの場合

nodeの管理にはバージョン管理ツールが必要なのですが、
個人的には n というツールが使いやすいです。

インストール後バージョン確認をしましょう。
僕の場合は以下のようになりました。

$ node -v
v8.6.0

$ npm -v
5.3.0

ローカルでの開発

create-react-app のインストール

$ npm install -g create-react-app

create-react-app によるプロジェクト作成

aidemy-frontend という名前で作成します

$ create-react-app aidemy-frontend

localhost:3000 で動作確認

以下を実行すると、 http://localhost:3000 で動作確認できると思います。

$ cd aidemy-frontend
$ npm start

スクリーンショット 2018-11-15 1.33.06.png

react-router-dom

ルーティングは react-router-dom を用いてやっていきます。
react-route-dom は react-router の一機能ですが、今回は事足りるということでこれでいきます。

react-router-dom をインストールします。
僕の場合はバージョンは 4.3.1でした

$ npm install react-router-dom

react-ace

エディター部分には React Ace を使用します。
こちらは、React用のエディターで、よしなにカスタマイズすることができます。

react-ace をインストールします。
僕の場合はバージョンは 6.2.0でした

$ npm install react-ace

App.jsの編集

フロントエンドのメイン箇所となるsrc/App.jsを編集していきます。


import React, { Component } from "react";
import { BrowserRouter, Route, Link } from "react-router-dom";
import AceEditor from "react-ace";
// react-aceのテーマ情報
import "brace/mode/python";
import "brace/snippets/python";
import "brace/theme/tomorrow";

// マスターデータ
const exercises = [
  {
    exerciseId: "exercise1",
    title: "1. Hello world",
    script: "# Hello worldを出力しましょう\n"
  },
  {
    exerciseId: "exercise2",
    title: "2. コメントの入力",
    script: "# 3 + 5 の結果を出力しましょう\n"
  }
];

// exerciseIdからexerciseを取得する関数
function getExercise(exerciseId) {
  return exercises.find(el => {
    return el.exerciseId === exerciseId;
  });
}

// 表示される土台の部分
// Link: クリック時にtoで指定されたページに遷移
// Route: 特定のurlの時に表示するコンポーネントを定義
const App = () => (
  <BrowserRouter>
    <div>
      <nav>
        <Link to="/">Home</Link>
      </nav>

      <hr />
      <Route exact path="/" render={props => <Exercises />} />
      <Route
        path="/exercises/:id"
        render={props => <Exercise match={props.match} />}
      />
    </div>
  </BrowserRouter>
);

// exerciseの一覧
class Exercises extends Component {
  constructor() {
    super();
    this.state = {
      exercises: []
    };
  }

  // コンポーネントのレンダリングが終了したら呼び出される
  componentDidMount() {
    this.setState({
      exercises
    });
  }

  // 実際の表示部分
  render() {
    return (
      <div>
        <h2>Exercises</h2>
        <p>エクササイズの一覧です</p>
        <ul>
          {(this.state.exercises || []).map(exercise => {
            return (
              <li key={exercise.exerciseId}>
                <Link
                  to={"/exercises/" + exercise.exerciseId}
                  key={exercise.exerciseId}
                >
                  {exercise.title}
                </Link>
              </li>
            );
          })}
        </ul>
      </div>
    );
  }
}

// 特定のexerciseの演習部分
class Exercise extends Component {
  constructor() {
    super();
    this.state = {
      exerciseId: "",
      title: "",
      script: ""
    };
    this.onChange = this.onChange.bind(this); // はじめにonChangeをthisにbindしておく
  }

  // コンポーネントのレンダリングが終了したら呼び出される
  componentDidMount() {
    const exercise = getExercise(this.props.match.params.id);
    if (exercise) {
      this.setState({
        exerciseId: exercise.exerciseId,
        title: exercise.title,
        script: exercise.script
      });
      return;
    }
    this.setState({
      exerciseId: "",
      title: "",
      script: ""
    });
  }

  // エディタに入力があるたびに呼び出され、stateを変更する
  onChange(newValue) {
    this.setState({
      script: newValue
    });
  }

  // RUN押下時に呼び出される関数。現状alertを表示するのみ。
  run() {
    alert(`以下のコードが実行されます\n\n${this.state.script}`);
  }

  // 実際の表示部分
  render() {
    return (
      <div>
        <h2>Exercise</h2>
        <p>{this.state.title}</p>
        <AceEditor
          mode="python"
          onChange={this.onChange}
          editorProps={{ $blockScrolling: true }}
          editorSetting="editorLightTheme"
          theme="tomorrow"
          value={this.state.script}
        />
        <input type="button" value="RUN" onClick={this.run.bind(this)} />
      </div>
    );
  }
}

export default App;

※静的ファイルをホスティングの準備(本番モード)

これは好みによるのでやらなくてもいいです。
Reactの起動方法には以下の二つがあります。

- 開発モード
- 本番モード

開発モード は文字通り開発に適したモードで、
WarningやErrorがコンソール画面でみれたり、ソースを書き換えるたびに再度レンダリングされます。
ただし、ソースの書き換えを見張っていたり、ログを多めに出すので、リソースはかなり食います。

本番モード は開発モードとは異なり、
WarningやErrorはあまりコンソールにはでません。
また、ビルドされた(静的なHTML, CSS, Javascript などに出力された)ファイルをホスティングするので、
元のソースが書き換わっても再レンダリングは発生しません。
なのでリソースを小さく抑えることができますが、変更があった場合は再度ビルドしてホスティングする必要があります。

今回はDockerで起動するときは本番モードで起動することにします。

serveのinstall

静的ファイルのホスティングのために serveをinstallします。
ちなみに、 npm install は 省略して npm i でもOKです。


$ npm i serve

package.jsonに以下を追加



{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
+   "serve": "serve -s build",
    "eject": "react-scripts eject"
  }
}

静的ファイルホスティング

はじめにビルドして静的ファイルを生成します。

$ npm run build

静的ファイルをホスティングします。
問題なければ http://localhost:5000 で立ち上がります。

$ npm run serve

Dockerによる操作

docker-compose で動作するために

aidemy-frontendフォルダ内にDockerfileを配置

 └ aidemy-frontend/
     └ Dockerfile

Dockerfileを記述

本番モードで起動

FROM node:8
WORKDIR /usr/src/app
COPY . .
RUN npm install
RUN npm run build
CMD npm run serve

開発モードで起動

FROM node:8
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD npm run start

aidemy-frontendフォルダと同じ階層にdocker-compose.yml 配置

 ├ aidemy-frontend/
 │ └ Dockerfile
 └ docker-compose.yml

docker-compose.ymlを記述


version: "3"
services:
  aidemy-frontend:
    build: ./aidemy-frontend
    image: aidemy-frontend
    environment:
      - ENV
    ports:
      - 5000:5000

※もし、開発モードで起動したい場合は、ポートを 5000:5000 -> 2000:2000 へ変更

docker-composeによる起動

docker imageの作成

以下のコマンドを実行すると、aidemy-frontendのimageが作成されます。

$ docker-compose build

imageの確認

Dockerのimage確認は以下のコマンドで行います。

$ docker images

以下のような出力が得られればOKです。

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
aidemy-frontend     latest              948452abd4d5        30 minutes ago      1.12GB
node                8                   3b7ecd51ffe5        7 days ago          889MB

aidemy-frontend は1.12GBもあるんですね。。
元となったnode:8 が889MBなので仕方ないですね。。
PCの容量にはお気をつけください!

フォアグラウンドでの起動

$ docker-compose up

http://localhost:5000 で動作すればOK
もし、ローカルでも起動していた場合バッティングするので、ローカルで起動したものを止めましょう。

※バックグラウンドでの起動

$ docker-compose up -d

※バックグラウンドで起動したプロセスの終了

$ docker-compose down

今回は以上になります。
Dockerの使い方には慣れてきたでしょうか?
次回は12/17にバックエンドを開発していきます!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5