前回まででReactとExpressの両方が動作する開発環境を作成したので、次はReactで画面を複数追加し、画面遷移できるようにしてみる。
ここでは例としてユーザー一覧とユーザー詳細画面を表示できるようにする。
画面の追加
複数の画面を表示するにはURLのパスに合わせて画面を切り替えて表示する仕組みが必要である。reactではreact-router
、react-router-dom
というパッケージが用意されているので、まずはこれを追加する。
cd front
pnpm install react-router
pnpm install react-router-dom
main.tsx
importを追加する。
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import UsersListPage from './pages/UsersListPage'
<React.StrictMode>
に囲まれた個所を以下のように修正する。
react-routerの機能によりpath
に指定されたパスに一致する画面が表示されるようになる。
<BrowserRouter>
<Routes>
<Route path="/users" element={<UsersListPage />} />
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>
App.tsx
先頭にimportを追加する。
import { useCallback } from 'react'
import { useNavigate } from 'react-router'
import { MouseEvent } from 'react'
MouseEventをimportしないとonClickの箇所でビルドエラーになる。
HTMLの箇所にリンクを追加する。
onClickにonClickUserListLink
を指定することにより、リンクをクリックされたらonClickUserListLink
を呼び出すようにしている。
<div>
<a href="/users" onClick={onClickUserListLink}>User List</a>
</div>
HTMLの上のコードに、リンクをクリックされたときの処理を追加する。
関数定義はパフォーマンス向上のためuseCallback
でラップしている。(メモ化というらしい)
ボタンやリンクを押した場合の画面遷移をスキップするためpreventDefault
を呼んでいる。
画面遷移にはuseNavigate
から取得した関数にパスを指定して呼び出す。
const navigate = useNavigate()
const onClickUserListLink = useCallback((e: MouseEvent) => {
e.preventDefault()
navigate("/users")
}, [navigate])
修正後のコードは以下のようになる。
import { useCallback } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
import { useNavigate } from 'react-router'
import { MouseEvent } from 'react'
function App() {
const navigate = useNavigate()
const onClickUserListLink = useCallback((e: MouseEvent) => {
e.preventDefault()
navigate("/users")
}, [navigate])
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div>
<a href="/users" onClick={onClickUserListLink}>User List</a>
</div>
</div>
)
}
export default App
pages/UsersListPage.tsx
ユーザー一覧画面(の予定)を追加する。
こちらには一覧からトップページへ戻るリンクを追加する。
トップページと同様、リンクがクリックされたらonClickTopLink
を呼び出し、トップページに画面遷移する。
import React, { MouseEvent, useCallback } from 'react'
import { useNavigate } from 'react-router'
function UsersListPage() {
const navigate = useNavigate()
const onClickTopLink = useCallback((e: MouseEvent) => {
e.preventDefault()
navigate("/")
}, [navigate])
return (
<>
<h1>User List</h1>
<div style={{ textAlign: "left" }}>
<a href="/" onClick={onClickTopLink}>top...</a>
</div>
</>
)
}
export default UsersListPage
ここまで出来たらフロントのデバッグサーバーを起動して動作を確認してみる。
cd front
pnpm dev
ブラウザで http://localhost:3000/ を開いて、トップページからUser Listリンクをクリックする。ユーザー一覧画面(の予定)が表示されれば成功である。
SPA(Single Page Application)として作成しているので、画面遷移してもページの再読み込みをしないことがブラウザのデバッグコンソールで確認できる。
ユーザー一覧画面とユーザー詳細画面の作成
続いて、ユーザー詳細画面を追加しユーザー一覧画面から画面遷移できるようにする。
main.tsx
<Routes>
の箇所にユーザー詳細画面の<Route>
を追加する。
path
に:id
のように記述することにより、パスパラメータとして画面側でパスの一部をパラメータとして取得できる。
<Route path="/users/:id" element={<UsersDetailPage />} />
models/UserModel.ts
ユーザーデータを管理するモデルを作成する。
export default interface UserModel {
id: number
account: string
password?: string
}
動作確認のため、テストデータも定義しておく。
const user1 = { id: 1, account: "admin", password: "hogehoge" } as UserModel
const user2 = { id: 2, account: "guest", password: "piyopiyo" } as UserModel
export const testUserList = [user1, user2]
UsersListPage.tsx
importを追加する。
viteで生成したプロジェクトではCSSファイルをインポートで読み込めるよう設定されてるので、別途作成したCSSファイルを読み込む。(CSSファイルは省略。github参照)
import UserModel, { testUserList } from '../models/UserModel'
import "./Users.css"
リンクをクリックされた時の処理を追加する。
onClickUserDetailLink
ではユーザー詳細画面に遷移する際、パスにユーザーIDを含めて指定している。ユーザー詳細画面ではこのIDを取得してユーザーごとの情報を表示する。
const userList = testUserList
const onClickUserDetailLink = useCallback((e: MouseEvent, user: UserModel) => {
e.preventDefault()
navigate("/users/" + user.id)
}, [navigate])
const onClickUserCreateLink = useCallback((e: MouseEvent) => {
e.preventDefault()
alert("create!!")
}, [])
const onClickUserDelete = useCallback((e: MouseEvent, user: UserModel) => {
e.preventDefault()
alert(`${user.account} delete!!`)
}, [])
リンクとユーザー一覧のテーブルを追加する。
一覧などを表示する場合、reactのJSXの箇所ではfor文が使用できないので、かわりにArray.prototype.map
メソッドを使用して配列中の要素を取り出し順次処理する。
<div style={{ textAlign: "left" }}>
<a href="/users/new" onClick={onClickUserCreateLink}>create...</a>
</div>
<section style={{ minWidth: "400px" }}>
<table style={{ width: "100%" }}>
<thead>
<tr>
<th>ID</th>
<th>Account</th>
<th>Detail</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{userList.map(item =>
<tr key={`user-${item.id}`}>
<td>{item.id}</td>
<td>{item.account}</td>
<td><a href={`/users/${item.id}`} onClick={e => onClickUserDetailLink(e, item)}>[Detail]</a></td>
<td><a href={`/users/${item.id}`} onClick={e => onClickUserDelete(e, item)}>[Delete]</a></td>
</tr>
)}
</tbody>
</table>
</section>
UsersDetailPage.tsx
ユーザー詳細画面を追加する。
main.tsx
で指定したパスパラメータにより、パスからid
を取得し、idをもとにユーザー情報を表示している。
input
に値を表示するにはvalue={変数}
として指定する。
import React, { MouseEvent, useCallback } from 'react'
import { useNavigate, useParams } from "react-router";
import { testUserList } from '../models/UserModel'
import "./Users.css"
function UsersDetailPage() {
const navigate = useNavigate()
const params = useParams()
const userId = parseInt(params["id"]!!)
const user = testUserList.filter(p => p.id === userId)[0]
const onClickUserListLink = useCallback((e: MouseEvent) => {
e.preventDefault()
navigate("/users")
}, [navigate])
const onSave = useCallback(async () => {
alert("save!!")
}, [])
return (
<>
<h1>User Detail</h1>
<div style={{ textAlign: "left" }}>
<a href="/users" onClick={onClickUserListLink}>list...</a>
</div>
<section style={{ minWidth: "400px" }}>
<dl>
<dt>ID</dt>
<dd>
{user.id}
</dd>
<dt>Account</dt>
<dd>
<input type="text" value={user.account} />
</dd>
<dt>Password</dt>
<dd>
<input type="text" value={user.password} />
</dd>
</dl>
<div>
<input type="submit" value="Save" onClick={onSave} />
</div>
</section>
</>
)
}
export default UsersDetailPage
以上で完成である。
デバッグサーバを起動してユーザー一覧からユーザー詳細画面が表示できれば成功である。
createやdelete、saveのリンクやボタンは動作しないが、実際のシステムではAPI経由でデータを取得したり更新する処理を実装していくことになる。
というわけで、次回の記事でAPI呼び出しの実装について説明する予定である。