再利用性の高いコンポーネントを作成する

# 再利用性の高いコンポーネントを作成する

汎用的なコンポーネントを作りたい！
でも、多すぎるpropsを渡していませんか？
以下の悪い例と良い例を見比べれば、
再利用性を高めるには、propsを適切に絞ることが重要だとわかるはず。

悪い例：
```jsx
function Table({
  data,
  columns,
  sortable = false,
  filterable = false,
  pagination,
  rowActions = [],
  headerStyle,
  rowStyle,
  striped = false,
  onRowClick,
}) {
  ...
}
```
良い例
```jsx
function Table<T>({ data, columns, onRowClick }) {...}
```

## 再利用性の高いテーブルコンポーネント作ってみよう！

以下は一般的なテーブルコンポーネントです。
propsとしてはdataとcolumnsだけを受け取っているのでバッチリOK。
（urlが渡されて、このコンポーネント内でフェッチしてもOK）

改善点はどこかな？
```tsx
type Column =  {
  key: string;
  header: string;
}

type TableProps = {
  data: {
    id: number;
    name: string;
    price: string;
    stock: string;
  }[];
  columns: Column[];
}

const Table = ({ data, columns }: TableProps) => {
  return (
    <table>
      <thead>
        <tr>
          {columns.map((column) => (
            <th key="column.key">{column.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row.id}>
            <td>{row.id}</td>
            <td>{row.name}</td>
            <td>{row.price}</td>
            <td>{row.stock}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

export default Table;

```
このままだとデータ構造が固定されてしまうね。
本当はどんなデータの形でも受け入れたいところ、、

### リファクタリング１：ジェネリクスを使う

```tsx
type Column<T> = {
  key: keyof T;
  header: string;
};

type TableProps<T> = {
  data: T[];
  columns: Column<T>[];
};

function Table<T extends { id: number }>({ data, columns }: TableProps<T>) {
  return (
    <table>
      <thead>
        <tr>
          {columns.map((column) => (
            <th key="column.key">{column.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row.id}>
            {columns.map((column) => (
              <td key={String(column.key)}>{String(row[column.key])}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

export default Table;

```
ジェネリクスを使用してデータ型を柔軟にしたよ。
セル（td）も繰り返し処理で記述できたよ。

```tsx
import "./App.css";
import Table from "./components/Table";

type Product = {
  id: number;
  name: string;
  price: string;
  stock: string;
};

function App() {
  const columns: Array<{
    key: keyof Product;
    header: string;
  }> = [
    { key: "id", header: "ID" },
    { key: "name", header: "Name"},
    { key: "price", header: "Price" },
    { key: "stock", header: "Stock" },
  ];

  const products: Product[] = [
    { id: 1, name: "Wireless Mouse", price: "$25.99", stock: "In Stock" },
    {
      id: 2,
      name: "Mechanical Keyboard",
      price: "$75.49",
      stock: "Out of Stock",
    },
    { id: 3, name: "USB-C Hub", price: "$45.00", stock: "In Stock" },
    {
      id: 4,
      name: "Noise-Cancelling Headphones",
      price: "$199.99",
      stock: "In Stock",
    },
    { id: 5, name: "Webcam", price: "$89.99", stock: "Out of Stock" },
  ];

  return (
    <>
      <Table<Product> columns={columns} data={products} />
    </>
  );
}

export default App;

```
親コンポーネントでは型を定義し、型を子コンポーネントに渡しているよ。

### リファクタリング２: セルに入る内容を柔軟化

```tsx
import { ReactNode } from "react";
import "./App.css";
import Table from "./components/Table";

type Product = {
  id: number;
  name: string;
  price: string;
  stock: string;
};

function App() {
  const columns: Array<{
    key: keyof Product;
    header: string;
    render?: (value: Product[keyof Product]) => ReactNode;
  }> = [
    { key: "id", header: "ID" },
    { key: "name", header: "Name", render: (value) => <a href="#">{value}</a> },
    { key: "price", header: "Price" },
    { key: "stock", header: "Stock" },
  ];

  const products: Product[] = [
    { id: 1, name: "Wireless Mouse", price: "$25.99", stock: "In Stock" },
    {
      id: 2,
      name: "Mechanical Keyboard",
      price: "$75.49",
      stock: "Out of Stock",
    },
    { id: 3, name: "USB-C Hub", price: "$45.00", stock: "In Stock" },
    {
      id: 4,
      name: "Noise-Cancelling Headphones",
      price: "$199.99",
      stock: "In Stock",
    },
    { id: 5, name: "Webcam", price: "$89.99", stock: "Out of Stock" },
  ];

  return (
    <>
      <Table<Product> columns={columns} data={products} />
    </>
  );
}

export default App;

```
columnsにオプショナルなrenderプロパティを追加したよ。
これで文字列に限らずなんでも入るようになったね。
この例では、リンクが入るようにしたよ。

```tsx
import { ReactNode } from "react";

type Column<T> = {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T]) => ReactNode;
};

type TableProps<T> = {
  data: T[];
  columns: Column<T>[];
};

function Table<T extends { id: number }>({ data, columns }: TableProps<T>) {
  return (
    <table>
      <thead>
        <tr>
          {columns.map((column) => (
            <th key="column.key">{column.header}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row.id}>
            {columns.map((column) => (
              <td key={String(column.key)}>
                {column.render
                  ? column.render(row[column.key])
                  : String(row[column.key])}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

export default Table;

```
renderプロパティの有無でセルの中身の表示を変えているよ。

### リファクタリング３：コンポーネントを細分化する

```tsx
import { ReactNode } from "react";

type Column<T> = {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T]) => ReactNode;
};

type TableProps<T> = {
  data: T[];
  columns: Column<T>[];
};

export default function Table<T extends { id: number | string }>({
  data,
  columns,
}: TableProps<T>) {
  return (
    <div>
      <table>
        <TableHeader columns={columns} />
        <tbody>
          {data.map((row) => (
            <TableRow key={row.id} row={row} columns={columns} />
          ))}
        </tbody>
      </table>
    </div>
  );
}

type TableHeaderProps<T> = {
  columns: Column<T>[];
};

function TableHeader<T>({ columns }: TableHeaderProps<T>) {
  return (
    <thead>
      <tr>
        {columns.map((column) => (
          <th key={String(column.key)}>{column.header}</th>
        ))}
      </tr>
    </thead>
  );
}

type TableRowProps<T> = {
  row: T;
  columns: Column<T>[];
};

function TableRow<T extends { id: number | string }>({
  row,
  columns,
}: TableRowProps<T>) {
  return (
    <tr key={row.id}>
      {columns.map((column) => (
        <TableCell
          key={String(column.key)}
          value={row[column.key]}
          render={column.render}
        />
      ))}
    </tr>
  );
}

type TableCellProps<T> = {
  value: T;
  render?: (value: T) => ReactNode;
};

function TableCell<T>({ value, render }: TableCellProps<T>) {
  return <td>{render ? render(value) : String(value)}</td>;
}

```
Tableコンポーネントを以下のように分けたよ。
- TableHeader
- TableRow
- TableCell

これで読みやすいし管理されているね。