React.ComponentProps で Props の受け渡しを楽にする

React×Typescript でよくあるのが **「同じ Props の型宣言を何度も書くのが面倒くさい！」** ということ。
しかし `ComponentProps` を使用すれば子コンポーネントの Props の型を参照できるので、簡潔に記述することができます。

# ComponentProps を使ってみる
## 全部の型を参照
例えば以下のような画像を表示するだけの処理があるとします。

```Item.tsx
import React, { FC } from 'react';

type Props = {
  image: {
    id: number;
    url: string;
  };
};

export const Item: FC<Props> = ({ image }) => (
  <img src={image.url} alt={String(image.id)} />
);
```

このコンポーネントを呼び出す場合、親にも同じ型宣言が必要になります。 

```List.tsx
import React, { ComponentProps } from 'react';
import { Item } from 'Item';

type Props = {
  image: {
    id: number;
    url: string;
  };
};

export const List = ({ image }: Props) => (
  <Item image={image} />
)
```

面倒ですね。

そこで登場するのが `ComponentProps`。
`ComponentProps` を使えば、上記 `List.tsx` の Props 内を `ComponentProps` で置き換えることができます。

```List.tsx
import React from 'react';
import { Item } from 'Item';

type Props = React.ComponentProps<typeof Item>;

export const List = ({ image }: Props) => (
  <Item image={image} />
)
```

`ComponentProps<typeof [コンポーネント]>`
と指定することで、対象コンポーネントに指定されている型を参照することができます。
今回の場合は `Item.tsx` で `export const Item: FC<Props> = ...` とジェネリクスに Props が指定されているので、Props を参照していることになります。

## 一部の型を参照

型を一部だけ参照することも可能です。
例えば下記 Props の中身から image だけ参照したい場合もあります。

```
type Props = {
  image: {
    id: number;
    url: string;
  };
  user: {
    id: number;
    name: string;
  };
};
```

その場合は、`ComponentProps<typeof Item>` の後にさらに参照したい部分だけ記述するといいでしょう。

```
type Props = {
  image: React.ComponentProps<typeof Item>['image'];
};
```

# 型の拡張・縮小を行う

`ComponentProps` で型の拡張・縮小も楽に行うことができます。

が、その前にまずは Typescript における type の操作方法について見ていきましょう。

## Type の操作方法

### 型の結合
型をそのまま結合します。

```
type A = {
  a1: string;
  a2: number;
};

type B = {
  b1: number;
  b2: string;
}

type AB = A & B;

// ↑の内容
type AB = {
  a1: string;
  a2: number;
  b1: number;
  b2: string;
}
```

### 型の抜き出し
指定した型のみで構築します。

```
type ABPick = Pick<AB, 'a1' | 'b1'>;

// ↑の内容
type ABPick = {
  a1: string;
  b1: number;
}
```

### 型の削除
指定した型を削除して構築します。

```
type ABOmit = Omit<AB, 'a1' | 'b1'>;

// ↑の内容
type ABPick = {
  a2: number;
  b2: string;
}
```

よく使いそうな物を紹介しました。
他にも Partial など沢山あるので、詳しくはこちらを参照してください。
https://www.typescriptlang.org/docs/handbook/utility-types.html

## ComponentProps の利用

それでは ComponentProps を利用します。

まずは上記 `Item.tsx` に要素を追加したくなったので、下記のように `User` コンポーネントを追加したとしましょう。

```Item.tsx
import React, { FC } from 'react';
import { User } from 'User';

type Props = {
  image: {
    id: number;
    url: string;
  };
  user: {
    id: number;
    name: string;
  };
};

export const Item: FC<Props> = ({ image, user }) => (
  <>
    <img src={image.url} alt={String(image.id)} />
    <User user={user} />
  </>
);
```

ここで User の型宣言を ComponentProps に変えて、
type の型結合を行えば下記のように すっきりした記述になります。

```Item.tsx
import React, { FC } from 'react';
import { User } from 'User';

// ComponentProps を利用する
type UserProps = React.ComponentProps<typeof User>;

type Props = {
  image: {
    id: number;
    url: string;
  };
} & UserProps; // UserProps を Props と結合する

export const Item: FC<Props> = ({ image, user }) => (
  <>
    <img src={image.url} alt={String(image.id)} />
    <User user={user} />
  </>
);
```

もし User の型宣言が

```
type Props = {
  user: {
    id: number;
    name: string;
  };
  avatar: {
    url: string;
  }
};
```

のようになっていて、user 部分だけ結合したいという場合は
Props 部分は下記のようになるでしょう。

```
type Props = {
  image: {
    id: number;
    url: string;
  };
} & Pick<UserProps, 'user'>;
```

# まとめ
ComponentProps を利用すればかなり快適に Props の受け渡しが楽になるのでおすすめです。