それでは、今回はより具体的にReact開発方法について解説します。
ショップデモのリポジトリの配下にreactというディレクトリがあります。
この下にReactのコードがあります。
ここのreact-setup.mdに記載されている通り、前準備としてreactのテンプレートを作ります。
npx create-react-app shopdemo --template typescript
Shell SessionShell Session
あとはこのReactプロジェクトを動かすためのライブラリのインストールを行います。
詳細は、react-setup.mdに書いてあります。
まず3つのディレクトリがあって、これは絶対こうしなければならないというものでもないのですが、基本的なお作法として用意するのが一般的なようです。
- public
- ここにはindex.htmlだけ置くのが一般的なようです。
- テンプレートが自動生成するものでも良いのですが、Bootstrapを使用する場合は、テンプレートのindex.htmlにそのライブラリのロードを付け加えています。
- components
- ここに自分で開発するreactコンポーネントを配置します。
- hooks
- hookを用意する場合は、ここに配置します。
ここでは、まずログインをするためのユーザー認証を行うコンポーネントの処理について説明します。
Login.tsxに処理を記述しています。
tsxという拡張子は、jsx形式のファイルで、typescriptで記述する際は、tsxにするのがお作法のようです。
このファイルの終わりの方にreturn文以降に以下のような記述があります。
これがjsx記法と呼ばれるもので、javascriptの中にHTML文を埋め込むことができます。
return (
<div>
<h1>ログイン</h1>
<form onSubmit={handleSubmit}>
<table>
<tbody>
<tr>
<td><label>利用者ID:</label></td>
<td><input name="userid" type="text" placeholder="userid" /></td>
</tr>
<tr>
<td><label>パスワード</label></td>
<td><input name="password" type="password" placeholder="password" /></td>
</tr>
<tr><td><button type="submit">ログイン</button></td></tr>
</tbody>
</table>
</form>
{isError && <p style={{ color: "red" }}>{`${errorText}`}</p>}
</div>
);
};
JavaScriptJavaScript
ここでユーザー名とパスワードを入力してもらって、ログイン認証を行う処理を作っていきます。
前回の記事でReactは基本SPAでサブミットはないという説明をしましたが、このログイン処理のように複数のデータをフォーム形式で入力してサブミットするというようなケースは多々あります。
方法は色々あるのですが、ここではreact-router-domというものを使っています。
それ以外にもnext.jsというフレームワークが有名です。
実際にサブミットしているように見えますが、実際にPOSTしているわけではなく、react-router-domというフレームワークの中であくまでもSPAの枠組みの中で処理は実装されている感じです。
(もしかしたらこの理解は間違っているかも)
ここでonSubmitコールバックとしてhandleSubmitというメソッドが呼ばれています。
ここの中括弧は、jsxの作法の1つで、そのカッコ内にjavascriptを記述できます。
ここでは、handleSubmitはJavaScriptの変数で、以下のように定義されています。
ちなみにReactでは変数定義は、基本const、たまにletが使われ、varは使いません。
古いJavaScriptしか知らない人にとってはここが1つのハードルかもしれません。
そして以下のコードは厳密に言うと、TypeScriptですが、あんまり褒められたTypeScriptコードではありません。
eventという変数の型としてanyを使っていますが、本当は適切なイベント型を指定するべきです。
そのイベントに紐づいた(ここはフォームデータなので)データ要素としてユーザー名とパスワードを取得しています。
そしてuserLoginCheckというメソッドを呼び出しています。
const handleSubmit = (event: any) => {
event.preventDefault();
const { userid, password } = event.target.elements;
userLoginCheck(userid.value, password.value).finally(() =>
{
if (login.status) {
navigate("/Shop", { state: { customerId: login.customerId } })
}
})
};
JavaScriptJavaScript
userLoginCheckの中身は以下のようになっています。
const userLoginCheck = async (userid: any, userpassword: any) => {
let status = false;
let customerId = 0;
setIsLoading(true);
setIsError(false);
await axios
.get<any>(`http://${serverAddress}:${serverPort}${applicationName}/checkpassword/${userid}/${userpassword}?IRISUsername=${username}&IRISPassword=${password}`)
.then((result: any) => {
if (result.data.authorized === 'ok') {
login.status = true;
login.customerId = result.data.ID;
}
else {
setIsError(true);
setErrorText('ログインが失敗しました');
}
})
.catch((error: any) => {
setIsError(true)
if (error.response) {
setErrorText(error.response.data.summary);
}
else if (error.request) {
setErrorText(error.request);
}
else {
setErrorText(error.message);
}
})
.finally(() => setIsLoading(false))
};
JavaScriptJavaScript
この後、RESTのインタフェースによりサーバーのAPIを呼び出します。
RESTのインタフェースの実装も複数ありますが、ネット上でサンプルがたくさん見つかるaxiosというライブラリを使用しています。
ここでREST APIのurlを指定します。
このurlにcheckpasswordというメソッド名が含まれているのがわかると思います。
これがIRIS側で呼ばれるメソッド名となります。
ログインチェックがOKだったら、react-router-domに含まれるnavigateメソッドを呼び出して、/Shopにページ遷移します。
(実際にページ遷移しているわけではなくあくまでもエミュレーション)
あと、axiosの呼び出しは非同期なので、結果が返る前に呼び出しが戻ってくるので、最初はその動きになかなか慣れないかもしれません。
ログインが成功するとShop.tsxが呼ばれます。
このページはさらに複数のコンポーネントで構成されています。
そしてショッピングカートの機能を実装するためにcreateContextを使いコンテキスト情報を管理します。
navigate経由でページ遷移する際にデータを引き継ぐ仕組みとしてuseLocationというフックが用意されているので、それを使って情報を引き継ぎます。
jsx記法によるHTML定義の中で動的にデータを変更したい部分にはuseStateというフックを使用します。
import React from 'react';
import { createContext, useState, Dispatch,SetStateAction } from "react";
import { Header } from './Header';
import { ShoppingCart } from './ShoppingCart';
import { ProductList } from './ProductList';
import { useLocation } from "react-router-dom"
export type shopItem = {
productCode: string;
productName: string;
price: number;
units: number;
};
export const ShopContext = createContext({} as {
orderItems: shopItem[];
setOrderItems: Dispatch<SetStateAction<shopItem[]>>;
}
);
export const Shop = () => {
const [orderItems, setOrderItems] = useState<shopItem[]>([]);
const location = useLocation();
const values={orderItems,setOrderItems};
return (
<>
<div className="title">
<Header customerId = {location.state.customerId} />
</div>
<ShopContext.Provider value={values}>
<div className="shoppingcart" style = {{ float: "left",width: "40%",height: "100%",overflow: "auto",border: "solid #000000 1px"}}>
<ShoppingCart customerId = {location.state.customerId} />
</div>
<div id="productlist" style = {{ width: "60%",height: "100%",overflow: "auto",border: "solid #000000 1px"}}>
<ProductList />
</div>
</ShopContext.Provider>
</>
);
}
export default Shop;
JavaScriptJavaScript