投稿日
サービス開発リファレンスを使って1画面作成してみよう
はじめに
こんにちは。西日本テクノロジー&イノベーション室の齊藤です。
私の所属する西日本テクノロジー&イノベーション室では、2020年9月末に「SPA + REST API構成のサービス開発リファレンス(以下サービス開発リファレンス)」を公開しました。こちらは、シングルページアプリケーション(以下SPA)とREST APIから構成されるWebアプリケーションを開発する際に活用して頂けるコンテンツになっています。
前回のブログでは、SPAとREST APIをつなぎ合わせて「Hello World」を表示するところまでご紹介しました。
今回はSPAに絞って、タスク管理をするための簡易的な画面(Todo画面)を作成します。
- サービス開発リファレンスを使ってWebアプリケーションを作成してみよう<導入編>
- サービス開発リファレンスを使って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('');
~~~~~
次に、サブミット時にTodoを追加する処理を実行するためにhandleSubmit関数
を実装し、formのonSubmit
に設定します。これで、サブミット時にこの関数がコールバックされます。
また、サブミット時に関数がコールバックされた後、そのままだとサブミットイベントによりフォームをサーバに送信しようとしてしまうので、関数内でevent.preventDefault()
を呼び、サブミットイベントをキャンセルしておきます。
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
setTodos([...todos, text]);
};
~~~~~
<<form className="form" onSubmit={handleSubmit}>
次に、フォームのサブミットで処理を行うように、「追加」ボタンのtype
をsubmit
に設定します。
これで、「追加」ボタンをクリックするとサブミットされるようになります。
<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 の「表示—継承」に準拠しています。