はじめに
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} と書くと、オブジェクトの中身を展開して渡すことができる