準備
まずは、Reactのプロジェクトを作り、MUIのインストールをしましょう。
開発環境は以下の通りになります。
- react: 18.2.0
- typescript: 4.9.5
- mui: 5.14.2
実装開始
準備が完了したら早速実装に入ります。
行の内容を定義
まずはテーブルを構成する行の定義をします。
今回は、以下のように4つの項目を持つ行を定義します。
type Row = {
no: number
name: string
age: number
note: string
};
実データをState管理
次に実際に扱うデータを作成し、State管理します。
まずは、実データの作成からです。
const initData: Row[] = [
{ no: 1, name: 'name1', age: 43, note: '野球が好き' },
{ no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
{ no: 3, name: 'name3', age: 25, note: '相模原が好き' },
{ no: 4, name: 'name4', age: 78, note: '相撲が好き' },
];
次は、作成したinitDataを初期値に持つStateを作成します。
const [rows, setRows] = useState<Row[]>(initData);
テーブルの描画
最後にテーブルを描画すれば完了になります。
return (
<>
<Table sx={{ maxWidth: 550 }} aria-label="simple table">
{/* ヘッダー */}
<TableHead >
<TableRow sx={{ backgroundColor: 'darkGray' }}>
<TableCell align="center">番号</TableCell>
<TableCell align="center">名前</TableCell>
<TableCell align="center">年齢</TableCell>
<TableCell align="left">備考</TableCell>
</TableRow>
</TableHead>
{/* ボディ */}
<TableBody>
{/* rows(行が格納された配列をループ処理) */}
{rows.map((row, index) => {
return (
<TableRow
key={`index-${row.no}`}
>
<TableCell align="center">{row.no}</TableCell>
<TableCell align="center">{row.name}</TableCell>
<TableCell align="center">{row.age}</TableCell>
<TableCell align="left">{row.note}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
);
ここでのポイントは、16行目から27行目までの処理になります。
ここでは、State管理しているrowsを要素数分ループするようにしています。
ここで便利なのがmapメソッドです。
16行目で(row, index)と記述していますが、ここの文字はなんでもOKです。
ただ、できるだけ分かりやすい名前にすると良いです。
ここでのrowは、ループ中の要素(1行ずつ)を表しています。
よって、row.nameなどRowの中の要素を取り出すことが可能です。
次にindexですが、これにはループ中の番号が0から順番に入ります。
今回は要素数が4なので0~3までの数字が入っています。
ちなみに、このindexは書かなくても問題ありません。
ただ、あると便利なときは多いです!!
全体像
最後に今まで解説をしたコードを載せておきますので、
実行してみて、挙動を確かめてみてください。
今回はすべてを1つのファイル(App.tsx)に記述していますが、
慣れている方は、もっと細かくコンポーネント化してみると良いと思います。
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import React, { useRef, useState } from 'react';
// 行の内容
type Row = {
no: number
name: string
age: number
note: string
};
function App(): JSX.Element {
// stateに入れる初期データ
const initData: Row[] = [
{ no: 1, name: 'name1', age: 43, note: '野球が好き' },
{ no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
{ no: 3, name: 'name3', age: 25, note: '相模原が好き' },
{ no: 4, name: 'name4', age: 78, note: '相撲が好き' },
];
// useStateの宣言と初期値の格納
const [rows, setRows] = useState<Row[]>(initData);
return (
<>
<Table sx={{ maxWidth: 550 }} aria-label="simple table">
{/* ヘッダー */}
<TableHead >
<TableRow sx={{ backgroundColor: 'darkGray' }}>
<TableCell align="center">番号</TableCell>
<TableCell align="center">名前</TableCell>
<TableCell align="center">年齢</TableCell>
<TableCell align="left">備考</TableCell>
</TableRow>
</TableHead>
{/* ボディ */}
<TableBody>
{/* rows(行が格納された配列をループ処理) */}
{rows.map((row, index) => {
return (
<TableRow
key={`index-${row.no}`}
>
<TableCell align="center">{row.no}</TableCell>
<TableCell align="center">{row.name}</TableCell>
<TableCell align="center">{row.age}</TableCell>
<TableCell align="left">{row.note}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
);
};
export default App;
カスタマイズ(行にボタンを追加)
今回は、もう少しカスタマイズしてみます。
行の中にボタンを追加してみようと思います。
JSXの拡張
まずは以下のようにJSXを拡張して最後の列にボタンを追加します。
これだけで、見た目上は完成になります。
ただ、機能もつけたいので、次で削除用の関数を追記していきます。
return (
<>
<Table sx={{ maxWidth: 550 }} aria-label="simple table">
{/* ヘッダー */}
<TableHead >
<TableRow sx={{ backgroundColor: 'darkGray' }}>
<TableCell align="center">番号</TableCell>
<TableCell align="center">名前</TableCell>
<TableCell align="center">年齢</TableCell>
<TableCell align="center">備考</TableCell>
<TableCell align="center">アクション</TableCell> {/* 追記 */}
</TableRow>
</TableHead>
{/* ボディ */}
<TableBody>
{/* rows(行が格納された配列をループ処理) */}
{rows.map((row, index) => {
return (
<TableRow
key={`index-${row.no}`}
>
<TableCell align="center">{row.no}</TableCell>
<TableCell align="center">{row.name}</TableCell>
<TableCell align="center">{row.age}</TableCell>
<TableCell align="center">{row.note}</TableCell>
<TableCell align="center">
<Button onClick={() => deleteTarget(index)}>削除</Button> {/* 追記 */}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
);
削除用の関数を定義
次は、削除ボタンを押したときに実行される関数を記述します。
const deleteTarget = (index: number): void => {
const tmpRow = Array.from(rows);
tmpRow.splice(index, 1);
setRows(tmpRow);
};
これが、意外とハマるポイントです。
2行目で、わざわざfrom関数を使用しているのには理由があります。
普通なら対象行を削除したrowsをそのままStateに格納すればうまくいきそうですよね?
でも、Reactではうまくいきません。
その理由はuseStateでは、参照が同じだと状態に変更があったと検知されないためです。
そのため、from関数を使用して値のみのコピーをして、別の参照としてsetしています。
このようにすることで、状態に変更があったと検知され、再描画が行われます。
※値のみコピーすることを「浅いコピー」や「シャローコピー」と呼びます。
※このようなシャローコピーなど便利な操作をサポートしてくれるライブラリも存在します。
※気になる方は「lodash」と検索してみてください。
全体像(ver.カスタマイズ)
カスタマイズしたコードの全体像も貼っておきます。
import { Button, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
import React, { useRef, useState } from 'react';
import { JsxElement } from 'typescript';
// 行の内容
type Row = {
no: number
name: string
age: number
note: string
};
function App(): JSX.Element {
// stateに入れる初期データ
const initData: Row[] = [
{ no: 1, name: 'name1', age: 43, note: '野球が好き' },
{ no: 2, name: 'name2', age: 13, note: 'ゴリラが好き' },
{ no: 3, name: 'name3', age: 25, note: '相模原が好き' },
{ no: 4, name: 'name4', age: 78, note: '相撲が好き' },
];
// useStateの宣言と初期値の格納
const [rows, setRows] = useState<Row[]>(initData);
// 対象行を削除する関数
const deleteTarget = (index: number): void => {
// シャローコピー
const tmpRow = Array.from(rows);
// コピーしたデータの対象行(対象行は引数のindexで特定可能)を削除
tmpRow.splice(index, 1);
// データのセット
setRows(tmpRow);
};
return (
<>
<Table sx={{ maxWidth: 550 }} aria-label="simple table">
{/* ヘッダー */}
<TableHead >
<TableRow sx={{ backgroundColor: 'darkGray' }}>
<TableCell align="center">番号</TableCell>
<TableCell align="center">名前</TableCell>
<TableCell align="center">年齢</TableCell>
<TableCell align="center">備考</TableCell>
<TableCell align="center">アクション</TableCell>
</TableRow>
</TableHead>
{/* ボディ */}
<TableBody>
{/* rows(行が格納された配列をループ処理) */}
{rows.map((row, index) => {
return (
<TableRow
key={`index-${row.no}`}
>
<TableCell align="center">{row.no}</TableCell>
<TableCell align="center">{row.name}</TableCell>
<TableCell align="center">{row.age}</TableCell>
<TableCell align="center">{row.note}</TableCell>
<TableCell align="center">
<Button onClick={() => deleteTarget(index)}>削除</Button>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
);
};
export default App;
最後に
ここまで読んでいただき、ありがとうございます。
MUIはすごく便利なライブラリでその中でもテーブルはかっこよくて、拡張性も高いです。
たくさん拡張して自分だけのテーブルを作るのも面白いと思います。
コメント