前ページでフロント・バック・データベースの接続確認が完了したので、ここからはメモアプリのUIと機能を実装していきます。
本章ではフロントエンドの検索・追加フォームから、任意のデータを検索・表示あるいは追加できるようにするのが目標です。
- 簡易UIの作成
- 準備中
- 準備中
簡易UIの作成
まずは簡易的なUI(User Interface)を作っていきます。UIとはすなわちアプリの概観です。
今回作成するUIに必要なコンポーネントは、次の4つです。
- Headerコンポーネント
- Formコンポーネント
- Listコンポーネント
完成イメージはこんな感じ。
まずは、コンポーネントファイルを格納するためのmodules
ディレクトリと、CSSファイルを格納するためのcss
ディレクトリを作成しましょう。ターミナルにてmemo-app/frontend/src
ディレクトリに移動したら、次のコマンドを実行してください。
mkdir modules
mkdir css
Headerコンポーネントを作る
ターミナルにてfrontend/modulesに移動したら、Header.jsxファイルを作成します。
import React from 'react';
import '../css/common.css';
import '../css/Header.css';
function Header(){
return(
<>
<header className="Header">
<ul>
<li className="H_logo">
<p>Books</p>
</li>
<li>
aiueo
</li>
</ul>
</header>
</>
);
}
export default Header;
次に、/frontend/cssディレクトリに移動して2つのCSSファイルを作成します。
common.css
/*setting*/
html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,
blockquote,pre,abbr,address,cite,code,del,dfn,em,img,
ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,
ol,ul,li,fieldset,form,label,legend,table,caption,
tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,
figcaption,figure,footer,header,hgroup,menu,nav,section,
summary,time,mark,audio,video {
font-size: 100%;
margin: 0;
padding: 0;
vertical-align: baseline;
border: 0;
outline: 0;
background: transparent;
}
body {
line-height: 1;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display: block;
}
nav ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
a {
font-size: 100%;
margin: 0;
padding: 0;
vertical-align: baseline;
background: transparent;
}
ins {
text-decoration: none;
color: #000;
background-color: #ff9;
}
mark {
font-weight: bold;
font-style: italic;
color: #000;
background-color: #ff9;
}
del {
text-decoration: line-through;
}
abbr[title],
dfn[title] {
cursor: help;
border-bottom: 1px dotted;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
hr {
display: block;
height: 1px;
margin: 1em 0;
padding: 0;
border: 0;
border-top: 1px solid #ccc;
}
label{
font-family:"Poppins";
font-size:18px;
}
input{
font-family:"Poppins";
font-size:15px;
color:#171717;
}
input{
width:100%;
height:40px;
border-radius:3px;
border:1px solid rgba(0, 0, 0, 0.1);
background:#FCFCFC;
margin:5px 0 0 0 ;
padding:0 18px;
box-sizing: border-box;
}
select{
width:100%;
height:40px;
border-radius:3px;
border:1px solid rgba(0, 0, 0, 0.1);
background:#FCFCFC;
display:inline-block;
margin:5px 0 0 0 ;
padding:0 18px;
box-sizing: border-box;
color:#161616;
font-size:15px;
appearance: none;
}
select::-ms-expand{
display:none;
}
option{
font-size:14px;
font-family:"Poppins";
}
textarea{
width:100%;
height:200px;
border-radius:3px;
border:1px solid rgba(0, 0, 0, 0.1);
background:#FCFCFC;
margin:5px 0 0 0 ;
padding:15px;
box-sizing: border-box;
color:#161616;
font-family:"Poppins";
font-size:15px;
}
/*setting more*/
body {
font-family:"Noto Sans JP", fot-tsukubrdgothic-std, fot-tsukuardgothic-std, toppan-bunkyu-midashi-go-std, "Poppins","Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
width:100%;
background:#F2F2F2;
margin:0px;
overflow-x: hidden;
}
ul{
padding-left:0px;
list-style-type: none;
margin:0;
}
p,a,h1,h2,h3,h4,div{
font-size:16px;
color:#000;
text-decoration: none;
font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", "Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
}
p,a,li{
letter-spacing: 0.5px;
font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", "Yu Mincho", YuMincho, "YuMincho", "Yu Gothic", "YuGothic", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Robot", serif;
}
/*scrollbar*/
::-webkit-scrollbar{
width: 15px;
}
::-webkit-scrollbar-track{
background: #fff;
border: none;
border-radius: 10px;
box-shadow: inset 0 0 0px #333333;
}
::-webkit-scrollbar-thumb{
background: #efefef;
border-radius: 10px;
box-shadow: none;
}
.common_frame{
width:100%;
max-width:900px;
margin:30px auto;
padding:30px;
box-sizing: border-box;
}
.common_frame form{
margin:10px 0;
}
.common_frame input{
margin:10px 0;
}
Header.css
header{
width:100%;
height:80px;
background:#e0e0e0;
}
header>ul{
width:100%;
height:100%;
padding:0 30px;
box-sizing:border-box;
display:flex;
justify-content:space-between;
}
header>ul>li{
height:100%;
display:flex;
align-items:center;
font-weight:bold;
}
header>ul>li>a>p{
font-size:16px;
font-weight:bold;
}
header>ul>li>ul{
display:flex;
justify-content: flex-end;
}
header>ul>li>ul>li{
margin: 0 0 0 15px;
}
header>ul>li>ul>li>a>p{
font-size:16px;
font-weight:bold;
}
最後に、frontend/src
ディレクトリ直下のApp.jsファイルを次のように更新します。
import React, { useState,useEffect } from 'react';
//Headerコンポーネントを読み込む(拡張子は省略可)
//import Header from './modules/Header';これでもOK
import Header from './modules/Header.jsx';
function App() {
return (
//ReactのStrictモードでHeaderコンポーネントを表示
<React.StrictMode>
<Header/>
</React.StrictMode>
);
}
export default App;
ブラウザを確認すると、次のような画面が表示されているはずです。
UIの表示には関係ありませんが、使用しておいた方が無難でしょう。
Formコンポ―ネントを作る
同じ要領で、Formコンポーネントを作成します。
/frontend/src/modulesディレクトリに移動して、Form.jsxファイルを作成します。
import React, { useState } from 'react';
import '../css/common.css';
function Form(){
return(
<>
<div className="Form common_frame">
<p>ユーザー名</p>
<input type="text" name=""></input>
<input type="submit" name="" value="検索する" className="submit" />
</div>
</>
);
}
export default Form;
/frontend/srcディレクトリのApp.jsファイルを変更して、Formコンポーネントを読み込み・表示します。
import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
function App() {
return (
<React.StrictMode>
<Header/>
<Form/>
</React.StrictMode>
);
}
export default App;
ブラウザを確認すると、次のような画面が表示されているはずです。
Listコンポーネントを作る
同じ要領で、Listコンポーネントを作成します。
/frontend/src/modulesディレクトリに移動して、List.jsxファイルを作成します。
import React from 'react';
import '../css/common.css';
import '../css/List.css';
function List() {
return (
<>
<div className="List common_frame">
<ul className="item">
<li>Day</li>
<li>Title</li>
<li>Content</li>
</ul>
<ul className="result">
<li>12/1</li>
<li>欲しいモノリスト</li>
<li>きつね、たぬき、ねこ、スイカ</li>
</ul>
</div>
</>
);
}
export default List;
次に、/frontend/cssディレクトリに移動してList.css
ファイルを作成します。
.List>ul{
width:100%;
height:40px;
display:flex;
flex-wrap:wrap;
}
.List>ul>li{
display:flex;
align-items: center;
padding:0 10px;
box-sizing: border-box;
}
.List>.result>li{
background:#fff;
}
.List>ul>li:nth-child(1){
width:10%;
}
.List>ul>li:nth-child(2){
width:30%;
}
.List>ul>li:nth-child(3){
width:60%;
}
/frontend/srcディレクトリのApp.jsファイルを変更して、Listコンポーネントを読み込み・表示します。
import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';
function App() {
return (
<React.StrictMode>
<Header/>
<Form/>
<List/>
</React.StrictMode>
);
}
export default App;
以上でUIの作成は完了です。ブラウザを確認すると次のような画面が表示されるはずです。
コンポーネント間でデータを受け渡す方法
続いては、Reactのコンポーネント間でデータを受け渡す方法について解説します。コンポーネント間でデータ伝送を行うテクニックは、この後の「POST通信の実装」で活用します。
本章で行う作業はすべて、Reactの理解を深めるための実験ですので実際に試す必要はありません。(試してもいいけど、終わったらもとに戻すこと)
App.jsからForm.jsxへデータを渡す
App.jsファイルに、変数を保持するためのuseStateと初期値を設定します。
const [toform, setToform] = useState({
test : "to Form.jsx"
});
次に、変数(useState)をFormコンポーネントに渡すため、return()
内のコードを次のように変更します。
//変更前
<Form />
//変更後
<Form toform={toform} />
toform
という新たな変数に、先ほど設定したuseStateの変数toform
が代入されました。代入されたデータはそのままFormコンポ―ネントに渡されます。
次に、Form.jsxを開いてください。データの受け渡しにはpropsと呼ばれる仕組みを利用しているので、Form関数の引数にpropsを設定します。
//変更前
function Form(){
//変更後
function Form(props) {
Form関数内で定数testを定義し、props.toform.test
を代入します。
const test = props.toform.test;
pops.変数名.オブジェクトkey
で取り出すことができます。オブジェクトを利用していない場合は、
props.変数名
で取り出せます。
最後に、データを代入した定数testを表示します。return();
内のinput
タグの次に、以下のコードを追加してください。
<p>{test}</p>
ブラウザを確認すると、検索するボタンの下に「to form」と表示されるはずです。
上手くいかなかった人のために、一応App.jsとForm.jsxのコードを置いておきます。
App.js
import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';
function App() {
const [toform, setToform] = useState({
test:"to Form.jsx"
});
return (
<React.StrictMode>
<Header/>
<Form toform={toform}/>
<List/>
</React.StrictMode>
);
}
export default App;
Form.jsx
import React, { useState } from 'react';
import '../css/common.css';
function Form(props){
const test = props.toform.test;
return(
<>
<div className="Form common_frame">
<p>ユーザー名</p>
<input type="text" name=""></input>
<input type="submit" name="" value="検索する" className="submit" />
<p>{test}</p>
</div>
</>
);
}
export default Form;
Form.jsxからApp.jsにデータを渡す
さっきと逆のことをします。
Form.jsxからApp.jsにデータを渡す場合も、必ずApp.js側でデータを保持するuseStateを設置しておく必要があります。先ほどの手順通り作業した人は、App.js側に既にuseStateが設置してあるはずですが、すでに初期値が設定してあるのでリセットします。
先ほど設置したApp.jsのuseStateの値を空白にしてください。
const [toform, setToform] = useState({
test:""
});
次に、FormコンポーネントにuseStateのsetToform
変数を渡します。useStateのtoform
変数は「データを渡すため」に利用しましたが、setToform
変数は「データを受け取るため」に利用します。
<Form toform={toform} setToform={setToform}/>
最後に、Form.jsx側でも変数を保持するuseStateを設置します。useState変数を設置したら先ほど渡したsetToform
変数に、toapp
変数を代入します。
//useStateを定義
const [toapp, setToapp] = useState({
test:"to App.js"
});
//setToform変数にデータを代入
props.setToform(toapp);
以上で作業完了です。ブラウザを確認すると、「to App.js」と表示されているはずです。
コンポーネント間のデータ伝送の仕組みは、なんとなくイメージできたでしょうか?
App.js
import React, { useState,useEffect } from 'react';
import Header from './modules/Header';
import Form from './modules/Form';
import List from './modules/List';
function App() {
const [toform, setToform] = useState({
test:""
});
return (
<React.StrictMode>
<Header/>
<Form toform={toform} setToform={setToform}/>
<List/>
</React.StrictMode>
);
}
export default App;
Form.jsx
import React, { useState } from 'react';
import '../css/common.css';
function Form(props){
const test = props.toform.test;
const [toapp, setToapp] = useState({
test:"to App.js"
});
props.setToform(toapp);
return(
<>
<div className="Form common_frame">
<p>ユーザー名</p>
<input type="text" name=""></input>
<input type="submit" name="" value="検索する" className="submit" />
<p>{test}</p>
</div>
</>
);
}
export default Form;
POST通信の実装
本章では、検索フォームに入力したキーワードを使ってMySQLからデータを取得・表示できるようにしていきます。