はじめに
HTML,CSSで表の作成をしたことはあったが、Reactでの経験がないので備忘録として残します。
時間短縮のためにサンプルコードはclaude codeで出力しました。
サンプルコード
Table.tsx
import styles from "./table.module.css"
type TableData = {
[key: string]: string | number;
};
type Props = {
headers: string[];
data: TableData[];
className?: string;
};
/**
* シンプルなテーブルコンポーネント
*
* @param param0 テーブルのヘッダーとデータ
* @param param0.headers テーブルのヘッダー配列
* @param param0.data テーブルのデータ配列
* @param param0.className 追加のCSSクラス名
* @returns JSX要素
*/
function Table({
headers,
data,
className = ""
}: Props) {
return (
<div className={`${styles["table-container"]} ${className}`}>
<table className={styles.table}>
<thead>
<tr>
{headers.map((header, index) => (
<th key={index} className={styles["table-header"]}>
{header}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIndex) => (
<tr key={rowIndex} className={styles["table-row"]}>
{headers.map((header, colIndex) => (
<td key={colIndex} className={styles["table-cell"]}>
{row[header]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
export default Table;
type TableData
type TableData = {
[key: string]: string | number;
};
type TableData = ...
→TableData
という名前で型を定義
代入ではなく型の定義
{ [key: string]: string | number }
→ インデックスシグネチャと呼ばれる書き方。
値は string または number
配列ではなくオブジェクト!!
具体例
// ✅ OK: TableData はオブジェクト
const row: TableData = {
name: "田中太郎",
age: 30,
};
// ❌ NG: 配列ではない
const wrong: TableData = ["田中太郎", 30]; // エラー
type Props
type Props = {
headers: string[];
data: TableData[];
className?: string;
};
Props
は 「このコンポーネントを使うときに渡せる引数の型」
代入ではなく型の定義をしている
各プロパティの説明
headers: string[]
表のヘッダー(列名)を文字列の配列で渡す
例: ["名前", "年齢", "メール"]
data: TableData[]
→ 表の中身(行データの配列)。
1行分は TableData 型(オブジェクト)
、それを複数集めた配列。
className?: string
→ 任意のCSSクラスを追加で渡せるようにする。
?
がついているので「省略してもOK」。
function Table
function Table({
headers,
data,
className = ""
}: Props) {
Table
という名前の関数を定義している
→コンポーネントとして使えるようになる
引数部分の分解
{ headers, data, className = "" }
- 通常なら
props.headers
のように書くけどfunction Table(props: Props)
- オブジェクトの分割代入 を使って直接
headers
やdata
として受け取っている- デフォルト値を空文字にしているので、
className
を渡さなくてもエラーにならない
- デフォルト値を空文字にしているので、
}: Props) {
引数全体がprops型だと表している
return
return (
<div className={`${styles["table-container"]} ${className}`}>
<table className={styles.table}>
<thead>
<tr>
{headers.map((header, index) => (
<th key={index} className={styles["table-header"]}>
{header}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIndex) => (
<tr key={rowIndex} className={styles["table-row"]}>
{headers.map((header, colIndex) => (
<td key={colIndex} className={styles["table-cell"]}>
{row[header]}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
<div className={${styles["table-container"]} ${className}}>
→ CSSモジュールのクラス table-container
と、外から渡された className
を合体
<thead>
部分
<thead>
<tr>
{headers.map((header, index) => (
<th key={index} className={styles["table-header"]}>
{header}
</th>
))}
</tr>
</thead>
headers 配列
を .map()
でループして <th>
を生成。
例: ["名前", "年齢"]
-
headers
= ["名前", "年齢", "メール"] だとする -
map
が順番に処理していくとき
-
header = "名前"
,index = 0
→<th key={0}>名前</th>
-
header = "年齢"
,index = 1
→<th key={1}>年齢</th>
-
header = "メール"
,index = 2
→<th key={2}>メール</th>
pythonのfor in
みたいなイメージ?
<tbody>
部分
<tbody>
{data.map((row, rowIndex) => (
<tr key={rowIndex} className={styles["table-row"]}>
{headers.map((header, colIndex) => (
<td key={colIndex} className={styles["table-cell"]}>
{row[header]}
</td>
))}
</tr>
))}
</tbody>
data
配列(行データ)を .map()
でループして <tr>
を生成。
-
tr
は行を表している
{data.map((row, rowIndex) => (
<tr key={rowIndex} className={styles["table-row"]}>
{headers.map((header, colIndex) => (
ここで取得するのは1行分
例:{ "名前": "田中太郎", "年齢": 25, "職業": "エンジニア", "部署": "開発部" }
{headers.map((header, colIndex) => (
<td key={colIndex} className={styles["table-cell"]}>
{row[header]}
</td>
-
headers.map
でheaders
を回している -
{row[header]}
で中身を取り出す
例:
- 1回目ループ →
header = "名前"
-
row[header]
=row["名前"]
="田中"
-
- 2回目ループ →
header = "年齢"
-row[header]
=row["年齢"]
=30
TableTest.ts
import Table from '../../component/Table';
function TableTest() {
const headers = ["名前", "年齢", "職業", "部署"];
const data = [
{ "名前": "田中太郎", "年齢": 25, "職業": "エンジニア", "部署": "開発部" },
{ "名前": "佐藤花子", "年齢": 30, "職業": "デザイナー", "部署": "デザイン部" },
{ "名前": "鈴木次郎", "年齢": 28, "職業": "マネージャー", "部署": "企画部" },
{ "名前": "高橋美咲", "年齢": 32, "職業": "アナリスト", "部署": "分析部" }
];
return (
<div style={{ padding: "20px" }}>
<h1>Tableコンポーネントテスト</h1>
<Table headers={headers} data={data} />
<h2 style={{ marginTop: "40px" }}>シンプルなテーブル</h2>
<Table
headers={["項目", "値"]}
data={[
{ "項目": "作成日", "値": "2023-09-17" },
{ "項目": "更新日", "値": "2023-09-17" },
{ "項目": "ステータス", "値": "アクティブ" }
]}
/>
</div>
);
}
export default TableTest;
<Table headers={headers} data={data} />
でTableコンポーネント
にheadersとdataを渡している
追記
表を2つ渡せるようにした
function DoubleTables({
table1,
table2,
className = ""
}: {
table1: Props;
table2: Props;
className?: string;
}) {
return (
<div className={`${styles["double-tables"]} ${className}`}>
<Table {...table1} />
<Table {...table2} />
</div>
);
}
...
は スプレッド構文(spread syntax)と呼ばれるもの
スプレッド構文の基本
{...obj}
と書くと、オブジェクトの中身を展開して渡すことができる