React + TypeScript (.tsx) で、AtomicDesign(アトミックデザイン)に基づいた「管理者登録画面」を設計・実装していきます。
この記事では、Atomic Designに特化した記事であるためtailwindCSSは今は導入していません。
Atomic Designを設計するためのポイント
ReactやNext.jsはコンポーネント単位を組み合わせてアプリを作り上げるフレームワークなので、下記の概念をしっかり意識することが上達の近道です。
| コンポーネント | 用途 |
|---|---|
| Atoms | 再利用可能・意味を持たない |
| Molecules | フォームの「部品」 |
| Organisms | 業務ロジックを持つ(state / API) |
| Templates | レイアウトのみ |
| Pages | ルーティングと画面単位 |
| 👉 管理者編集画面・一覧画面を作るときも | |
| Atoms / Molecules をそのまま使い回せるのが強みです。 |
完成イメージ
ページルーティングのライブラリを導入
まず最初に、Reactでは、ページルーティングのためのライブラリを導入していきます。
react-router-dom
アトミックデザインの構成
src/
├─ components/
│ ├─ atoms/
│ │ ├─ Button.tsx
│ │ ├─ Input.tsx
│ │ ├─ Checkbox.tsx
│ │ └─ Label.tsx
│ ├─ molecules/
│ │ ├─ FormField.tsx
│ │ └─ PasswordField.tsx
│ ├─ organisms/
│ │ └─ AdminUserForm.tsx
│ └─ templates/
│ └─ AdminUserTemplate.tsx
├─ pages/
│ └─ AdminUserRegisterPage.tsx
つぎは、いよいよ各コンポーネントを作成していきます。
srcフォルダ配下に新規フォルダcomponentsを作成します。
componentsフォルダの中には、atomesフォルダ、moleculesフォルダ、organismsフォルダ、templatesフォルダをそれぞれ作成しておきます。
Atoms(最小単位)の作成
type Props = {
value: string;
onChange: (e:React.ChangeEvent<HTMLInputElement>)=>void;
type?: string;
name?: string;
}
export const Input:React.FC<Props>=({
value,
onChange,
type = "text",
name,
})=>(
<input
type={type}
name={name}
value={value}
onChange={onChange}
style={{ padding: "8px", width: "100%" }}
/>
);
type Props = {
text:string;
}
export const Label:React.FC<Props> = ({ text })=>(
<label style={{ display: "block", marginBottom: "4px" }}>
{ text }
</label>
);
type Props = {
text: string;
onClick?: () => void;
type?: "button" | "submit";
};
export const Button: React.FC<Props> = ({
text,
onClick,
type = "button",
}) => (
<button
type={type}
onClick={onClick}
style={{ padding: "8px 16px" }}>
{text}
</button>
);
type Props = {
checked: boolean;
onChange:(e:React.ChangeEvent<HTMLInputElement>)=>void;
}
export const Checkbox:React.FC<Props> = ({ checked, onChange })=>(
<input type="checkbox" checked={checked} onChange={onChange}/>
)
Molecules(意味を持つ部品)の作成
FormField.tsx(Label + Input)を作成します。
import { Input } from "../atoms/Input";
import { Label } from "../atoms/Label";
type Props = {
label: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
type?: string;
};
export const FormField: React.FC<Props> = ({
label,
value,
onChange,
type,
})=>(
<div style={{ marginBottom: "12px" }}>
<Label text={label}/>
<Input value={value} onChange={onChange} type={type}/>
</div>
)
import { FormField } from "./FormField";
type Props = {
value: string;
onChange:(e:React.ChangeEvent<HTMLInputElement>)=>void;
};
export const PasswordField:React.FC<Props> = ({
value,
onChange,
})=>(
<FormField
label="パスワード"
value={value}
onChange={onChange}
type="password"
/>
)
Organisms(フォーム全体)
import { useState } from "react";
import { PasswordField } from "../molecules/PasswordField";
import { FormField } from "../molecules/FormField";
import { Checkbox } from "../atoms/Checkbox";
import { Button } from "../atoms/Button";
export const AdminUserForm:React.FC = ()=>{
const [id,setId] = useState("");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [adminrole, setAdminrole] = useState(false);
const handleSubmit = async(e:React.FormEvent)=>{
e.preventDefault();
const body = {
id,
name,
email,
password,
adminrole
};
await fetch("/api/adminusers",{
method:"POST",
headers:{ "Content-Type": "application/json" },
body:JSON.stringify(body),
});
alert("管理者を登録しました");
};
return (
<form onSubmit={handleSubmit}>
<FormField label="社員番号" value={id} onChange={(e)=>setId(e.target.value)}/>
<FormField label="氏名" value={name} onChange={(e)=>setName(e.target.value)}/>
<FormField label="メールアドレス" value={email} onChange={(e)=>setEmail(e.target.value)}/>
<PasswordField value={password} onChange={(e) => setPassword(e.target.value)} />
<div style={{ marginBottom: "12px" }}>
<label>
<Checkbox checked={adminrole} onChange={(e)=>setAdminrole(e.target.checked)}/>
管理者権限
</label>
<Button type="submit" text="登録"/>
</div>
</form>
)
}
Template(レイアウト)の作成
import { AdminUserForm } from "../organisms/AdminUserForm";
export const AdminUserTemplate: React.FC = () => (
<div style={{ maxWidth: "400px", margin: "0 auto" }}>
<h1>管理者登録</h1>
<AdminUserForm />
</div>
);
Page(ルーティング単位)の作成
srcフォルダ配下に新規フォルダpagesを作成します。
import { AdminUserTemplate } from "../components/templates/AdminUserTemplate";
export const AdminUserRegisterPage:React.FC = ()=>{
return <AdminUserTemplate />
}
React では URL(パス)と Page コンポーネントをルーティングで結び付けることで
AdminUserRegisterPage を表示します。
ここでは React Router v6 を使う前提で説明します(現在の主流です)。
ルーティング設定(App.tsx)
import './App.css'
import { BrowserRouter, Routes, Route } from 'react-router-dom'// react-router-domを追加
import { AdminUserRegisterPage } from './pages/AdminUserRegisterPage'
function App() {
return (
<BrowserRouter>
<Routes>
<Route
path="/admin/users/new"
element={<AdminUserRegisterPage />}
/>
</Routes>
</BrowserRouter>
)
}
export default App
実際にアクセスするURL
ローカル開発環境(例:Vite / CRA)の場合:
http://localhost:3000/admin/users/new
※ Vite ならポートは 5173 になることもあります。
http://localhost:5173/admin/users/new
URL設計の考え方(REST寄り)
| 目的 | URL |
|---|---|
| 管理者登録 | /admin/users/new |
| 管理者一覧 | /admin/users |
| 管理者詳細 | /admin/users/:id |
| 管理者編集 | /admin/users/:id/edit |
おまけ メニューやボタンから遷移する場合
import { Link } from "react-router-dom";
<Link to="/admin/users/new">管理者登録</Link>
以上です。
サイト
Atomic Designの概念
【Atomic Design】Xの画面を見ながらAtomic Designを学ぶ
アトミックデザインをReactで導入してみた
React で実装する atomic design のコンポーネントごとの責務の分け方とコード規約
