準備
まずは、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はすごく便利なライブラリでその中でもテーブルはかっこよくて、拡張性も高いです。
たくさん拡張して自分だけのテーブルを作るのも面白いと思います。
 
  
  
  
  
コメント