Fintan

サービス開発リファレンスを使って1画面作成してみよう

CONTENTS

はじめに

こんにちは。西日本テクノロジー&イノベーション室の齊藤です。

私の所属する西日本テクノロジー&イノベーション室では、2020年9月末に「SPA + REST API構成のサービス開発リファレンス(以下サービス開発リファレンス)」を公開しました。こちらは、シングルページアプリケーション(以下SPA)とREST APIから構成されるWebアプリケーションを開発する際に活用して頂けるコンテンツになっています。

前回のブログでは、SPAとREST APIをつなぎ合わせて「Hello World」を表示するところまでご紹介しました。
今回はSPAに絞って、タスク管理をするための簡易的な画面(Todo画面)を作成します。

  1. サービス開発リファレンスを使ってWebアプリケーションを作成してみよう<導入編>
  2. サービス開発リファレンスを使って1画面作成してみよう(←今回の記事)

目次

事前準備

前回のブログの「サービス開発リファレンスを使ってWebアプリケーションを作成してみよう<導入編>」の完成状態をベースに作成しますので、こちらの作業がまだの方は実施してみてください。

ページ外観の作成

まずはTodoの内容を静的に定義して、次のような画面を作成します。

Reactではスタイルの記述方法がいくつか提供されていますが、CSSファイルをそのまま使えるようにclassName属性とCSSファイルを使ってスタイルを定義します。

次のようにCSSファイルを作成します。

frontend/src/example/components/pages/Todo.css

.content {
  margin-top: 10px;
  width: 40%;
  padding: 0 30%;
}
.form_field {
  margin-top: 20px;
  margin-bottom: 20px;
}
.form {
  width: 100%;
  display: flex;
  justify-content: space-between;
}
.input_field {
  width: 86%;
}
.input_field input{
  float: left;
  width: 95%;
  border-radius: 5px;
  padding: 8px;
  border: solid 1px lightgray;
  background-color: #fafbfc;
  font-size: 16px;
  outline: none;
}
.input_field input:focus {
  background-color: white;
}
.button_field {
  text-align: center;
  width: 14%;
}
.button_field button {
  height: 35px;
  cursor: pointer;
  line-height: 1;
  font-size: 1rem;
  color: white;
  background-color: black;
  border-radius: 5px;
  padding: 0 15px;
  border: none;
  vertical-align: middle;
}
.list {
  list-style: none;
  padding: 0;
  margin: 20px 0;
}
.item {
  padding: 15px 10px;
  background: whitesmoke;
  border: solid 1px lightgray;
  margin-bottom: 10px;
}
.todo {
  margin-left: 10px;
  text-align: left;
}

次に、Todoを表示するためのコンポーネントを作成します。
Todoの内容は現時点では静的に定義し、後ほど登録できるようにします。

frontend/src/example/components/pages/Todo.tsx

import React from 'react';
import './Todo.css';

const Todo: React.FC = () => {
  
  return (
      <div className="content">
        <div className="form_field">
          <form className="form">
            <div className="input_field">
              <input type="text" placeholder="やることを入力してください" />
            </div>
            <div className="button_field">
              <button type="button">追加</button>
            </div>
          </form>
        </div>
        <ul className="list">
          <li className="item">
            <div className="todo">
              <span>洗濯物を干す</span>
            </div>
          </li>
          <li className="item">
            <div className="todo">
              <span>部屋を掃除する</span>
            </div>
          </li>
        </ul>
      </div>
  );
};

export default Todo;

次に、frontend/src/App.tsx を以下のように変更して、先ほど作成したTodoのコンポーネントを表示するようにします。

import React from 'react';
import { Logger } from './framework/logging';
import './App.css';
import Todo from "./example/components/pages/Todo";

const App = () => {
  Logger.debug('rendering App...');
  return (
      <Todo />
  );
};

export default App;

ここで一度、frontend ディレクトリで次のコマンドを打って動作確認をします。

$ npm start

自動でブラウザが立ち上がり、Todoアプリの画面が表示されていれば成功です。
※ 自動で立ち上がらない場合は以下URLをブラウザで表示してみてください。
http://localhost:3000/

コンポーネントの実装

現時点ではTodoの内容を静的に定義しているため、今度は動的にTodoを表示できるようにします。

stateの利用

Reactでは、stateを使用することで状態の変化を表現できます(参考:React – Reactの流儀 Step 3

Reactの関数コンポーネントでは、様々な機能を実装するためにフック(Hooks)と呼ばれる機能が提供されており、stateの管理にはステートフックを使います(参考:ステートフックの利用法

ステートフックはuseStateを呼び出すことで使用できます。
引数に初期値を指定し、返り値としてstateとそれを更新するための関数をペアで返します。

const [todos, setTodos] = useState<string[]>([]);

また、関数コンポーネントでは、データの取得や更新によりコンポーネントに影響を与えることを副作用と呼び、副作用を起こす処理を実装するためのフックとして、副作用フックが提供されています(参考:React – 副作用フックの利用方法

副作用フックはuseEffectを呼び出すことで使用します。
第1引数に副作用を起こす関数と、第2引数にこの副作用が依存する値を配列で渡します。第2引数に渡した値が更新されると、第1引数の関数が実行されます。
最初のレンダー後に1度だけ呼び出したい場合には、空の配列([])を渡します(参考:React – 副作用を使う場合のヒント(最後の補足)

REST APIの呼び出し(※)を想定し、副作用フックを使用して静的データでstateを更新するように実装します。
※ REST APIとの繋ぎこみは、次回以降に実装します。

useEffect(() => {
    setTodos([ '洗濯物を干す', '部屋を掃除する']);
  }, []);

次に、Todoの内容を静的に定義している箇所を、stateから取得して表示するように変更します。

<ul className="list">
  {todos.map((todo, index) =>
      <li className="item" key={index}>
        <div className="todo">
          <span>{todo}</span>
        </div>
      </li>
  )}
</ul>

ここまで実装すると、Todo.tsxは次のようになっています。

import React, {useEffect, useState} from 'react';
import './Todo.css';

const Todo: React.FC = () => {
  const [todos, setTodos] = useState<string[]>([]);

  useEffect(() => {
    setTodos([ '洗濯物を干す', '部屋を掃除する']);
  }, []);

  return (
      <div className="content">
        <div className="form_field">
          <form className="form">
            <div className="input_field">
              <input type="text" placeholder="やることを入力してください" />
            </div>
            <div className="button_field">
              <button type="button">追加</button>
            </div>
          </form>
        </div>
        <ul className="list">
          {todos.map((todo, index) =>
              <li className="item" key={index}>
                <div className="todo">
                  <span>{todo}</span>
                </div>
              </li>
          )}
        </ul>
      </div>
  );
};

export default Todo;

ここで、frontend ディレクトリで次のコマンドを打って動作確認をします。

$ npm start

自動でブラウザが立ち上がり、さきほどと同じTodoアプリの画面が表示されていれば成功です。

useInputの利用

次に、テキストボックスにTodoの内容を入力して追加ボタンを押すことで、Todoが追加されるようにします。

テキストボックスを実装するために、example-chatではuseInputという独自のフック(frontend/src/framework/hooks/index.ts)を実装していますので、このフックを使用します。

useInputでは、useStateと同様に呼び出し時に初期値を渡します。戻り値としては、state自体や、inputに渡すためのプロパティ(value, onChange属性など)が設定されたオブジェクト等が返されます。
そしてスプレッド構文を使用して、返却されたプロパティのオブジェクトをinput要素に設定します。

const [text, textAttributes] = useInput('');

~~~~~

<input type="text" {...textAttributes} placeholder="やることを入力してください"/>

次に、サブミット時にTodoを追加する処理を実行するためにhandleSubmit関数を実装し、formのonSubmitに設定します。これで、サブミット時にこの関数がコールバックされます。
また、サブミット時に関数がコールバックされた後、そのままだとサブミットイベントによりフォームをサーバに送信しようとしてしまうので、関数内でevent.preventDefault()を呼び、サブミットイベントをキャンセルしておきます。

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault();
  setTodos([...todos, text]);
};

~~~~~

<<form className="form" onSubmit={handleSubmit}>

次に、フォームのサブミットで処理を行うように、「追加」ボタンのtypesubmitに設定します。
これで、「追加」ボタンをクリックするとサブミットされるようになります。

<button type="submit">追加</button>

ここまで実装すると、Todo.tsxは次のようになっています。

import React, {useEffect, useState} from 'react';
import {useInput} from "../../../framework";
import './Todo.css';

const Todo: React.FC = () => {
  const [todos, setTodos] = useState<string[]>([]);
  const [text, textAttributes] = useInput('');

  useEffect(() => {
    setTodos([ '洗濯物を干す', '部屋を掃除する']);
  }, []);

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setTodos([...todos, text]);
  };

  return (
      <div className="content">
        <div className="form_field">
          <form className="form" onSubmit={handleSubmit}>
            <div className="input_field">
              <input type="text" {...textAttributes} placeholder="やることを入力してください" />
            </div>
            <div className="button_field">
              <button type="submit">追加</button>
            </div>
          </form>
        </div>
        <ul className="list">
          {todos.map((todo, index) =>
              <li className="item" key={index}>
                <div className="todo">
                  <span>{todo}</span>
                </div>
              </li>
          )}
        </ul>
      </div>
  );
};

export default Todo;

ここまで実装できたら、テキストボックスにTodoの内容を入力して追加ボタンを押してみましょう。
入力した内容が、一番下に追加されているはずです。

※ 今回ご紹介した useInput 以外にもラジオボタンやチェックボックス、テキストエリアなどに対応したフックもご用意しています。詳しい使い方は frontend/src/framework/hooks/index.ts のJSDocをご覧ください。

ルーティングの設定

SPAでは1つのページを動的に書き換えるため、ページ内容が書き換わってもそのままではURLは変わりません。しかし、ブックマークやページ履歴を利用したい場合等、ページ内容に応じてURLを変更したい場面があります。
トップページと前回作成したHello Worldを表示する画面、そして今回作成したTodoページのURLを分けてみます。

  • / :トップページ
  • /todo :Todoページ
  • /hello :Hello Worldページ

ルーティングを実現するために、React用のルーティングライブラリであるReact Routerを使用します。

React Routerを使用することで、URLごとに使用するコンポーネントを制御したり異なるURLへ移動したりといったことを、簡単に実装できます。

frontend/src/App.tsx を次のように変更します。
(Topページは簡易にするためApp.tsxに直接書きましたが、コンポーネントに分けて実装してもよいです)

import React from 'react';
import { Logger } from './framework/logging';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import HelloWorld from "./example/components/pages/HelloWorld";
import Todo from "./example/components/pages/Todo";
import './App.css';

const App = () => {
  Logger.debug('rendering App...');
  return (
      <Router>
        <Switch>
          <Route exact path="/">
            <div>Topページ</div>
          </Route>
          <Route exact path="/todo">
            <Todo />
          </Route>
          <Route exact path="/hello">
            <HelloWorld />
          </Route>
        </Switch>
      </Router>
  );
};

export default App;

これで準備はできましたので、frontend ディレクトリで次のコマンドを打って動作確認をします。
※ すでに起動している場合は、ブラウザをリロードするだけで大丈夫です。

$ npm start

自動でブラウザが立ち上がり、Topページが表示されていると思います。

では次に、以下のURLを開いてみましょう。 http://localhost:3000/todo

Todoページが表示されていれば成功です。
このように、簡単にURLで画面を分けることができました。

※ URLで切り替えることはできましたが、画面のtitle要素(上記画像ではHello World)が切り替わらないため、変更する場合はexample-chatで用意しているusePageTitleという独自のフック(frontend/src/framework/hooks/index.ts)を各コンポーネントで使用することで変更できます。

usePageTitle('Todoページ');

まとめ

今回は、サービス開発リファレンスを使ってTodoページを実装しました。
次回はAPI作成の手順をご紹介します。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。

このエントリーをはてなブックマークに追加


TIS株式会社
個人情報保護方針 個人情報の取り扱いについて 情報セキュリティ方針 当サイトのご利用にあたって

Copyright 2021 TIS Inc.keyboard_arrow_up