2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Reactで画面を実装してみた

Last updated at Posted at 2022-09-11

前回まででReactとExpressの両方が動作する開発環境を作成したので、次はReactで画面を複数追加し、画面遷移できるようにしてみる。

ここでは例としてユーザー一覧とユーザー詳細画面を表示できるようにする。

画面の追加

複数の画面を表示するにはURLのパスに合わせて画面を切り替えて表示する仕組みが必要である。reactではreact-routerreact-router-domというパッケージが用意されているので、まずはこれを追加する。

cd front
pnpm install react-router
pnpm install react-router-dom

main.tsx

importを追加する。

main.tsx
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import UsersListPage from './pages/UsersListPage'

<React.StrictMode>に囲まれた個所を以下のように修正する。
react-routerの機能によりpathに指定されたパスに一致する画面が表示されるようになる。

main.tsx
    <BrowserRouter>
      <Routes>
        <Route path="/users" element={<UsersListPage />} />
        <Route path="/" element={<App />} />
      </Routes>
    </BrowserRouter>

App.tsx

先頭にimportを追加する。

App.tsx
import { useCallback } from 'react'
import { useNavigate } from 'react-router'
import { MouseEvent } from 'react'

MouseEventをimportしないとonClickの箇所でビルドエラーになる。

HTMLの箇所にリンクを追加する。
onClickにonClickUserListLinkを指定することにより、リンクをクリックされたらonClickUserListLinkを呼び出すようにしている。

App.tsx
      <div>
        <a href="/users" onClick={onClickUserListLink}>User List</a>
      </div>

HTMLの上のコードに、リンクをクリックされたときの処理を追加する。
関数定義はパフォーマンス向上のためuseCallbackでラップしている。(メモ化というらしい)
ボタンやリンクを押した場合の画面遷移をスキップするためpreventDefaultを呼んでいる。
画面遷移にはuseNavigateから取得した関数にパスを指定して呼び出す。

App.tsx
  const navigate = useNavigate()

  const onClickUserListLink = useCallback((e: MouseEvent) => {
    e.preventDefault()
    navigate("/users")
  }, [navigate])

修正後のコードは以下のようになる。

App.tsx
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を呼び出し、トップページに画面遷移する。

pages/UsersListPage.tsx
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)として作成しているので、画面遷移してもページの再読み込みをしないことがブラウザのデバッグコンソールで確認できる。

image.png

image.png

ユーザー一覧画面とユーザー詳細画面の作成

続いて、ユーザー詳細画面を追加しユーザー一覧画面から画面遷移できるようにする。

main.tsx

<Routes>の箇所にユーザー詳細画面の<Route>を追加する。
path:idのように記述することにより、パスパラメータとして画面側でパスの一部をパラメータとして取得できる。

main.tsx
        <Route path="/users/:id" element={<UsersDetailPage />} />

models/UserModel.ts

ユーザーデータを管理するモデルを作成する。

models/UserModel.ts
export default interface UserModel {
    id: number
    account: string
    password?: string
}

動作確認のため、テストデータも定義しておく。

models/UserModel.ts
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参照)

UsersListPage.tsx
import UserModel, { testUserList } from '../models/UserModel'
import "./Users.css"

リンクをクリックされた時の処理を追加する。
onClickUserDetailLinkではユーザー詳細画面に遷移する際、パスにユーザーIDを含めて指定している。ユーザー詳細画面ではこのIDを取得してユーザーごとの情報を表示する。

UsersListPage.tsx
  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メソッドを使用して配列中の要素を取り出し順次処理する。

UsersListPage.tsx
      <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={変数}として指定する。

UsersDetailPage.tsx
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

以上で完成である。
デバッグサーバを起動してユーザー一覧からユーザー詳細画面が表示できれば成功である。

image.png

image.png

createやdelete、saveのリンクやボタンは動作しないが、実際のシステムではAPI経由でデータを取得したり更新する処理を実装していくことになる。

というわけで、次回の記事でAPI呼び出しの実装について説明する予定である。

サンプルコード

https://github.com/betarium/sample-react-express/tree/0.3.0

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?