知ってると得をするReactコンポーネントのイケてる書き方

## はじめに

基本的にReact + TypeScriptでフロントの開発をしているんですが、実際にコードを書いている時に気をつけていること、便利な書き方として知っておくと得をするReactコンポーネントの書き方を紹介します。

## Propsが多くなりすぎたら

やたらpropsが多くなってしまうことありませんか？しかも同じような名称ばっかりを何回も書くことになるという。そうゆうときはできる限りショートハンドで書きましょう。

```jsx
return (
	<SampleComponent
		type={user.type}
		name={user.name}
		email={user.email}
		image={user.image}
	/>
)
```

Componentに全てのPropsを渡す場合は下記のようにするとコード量がだいぶ減りますね。

```jsx
return (
	<SampleComponent {...user} />
)
```

Component側に渡すのが全てではなくいくつか決められたものの場合はこちらです。

```jsx
const {type, name, email, image} = user
return (
	<SampleComponent {...{type, name, email, image}} />
)
```

とはいえ、ショートハンドを覚えるのも大事ですが、Propsが多すぎる場合にはそもそComponentの分割を検討した方が良いでしょう。

## React.FCの拡張

ComponentをCSS modulesやCSS in JSで作る際に `className` をPropsで渡せるようにしたいですよね？もちろん下記のようにすればclassNameを渡せるんですけど、いちいち数十個、数百個もあるComponentに対して型定義するのは正直めんどくさい。

```jsx
type Props = {
	className?: string
}

export const Component: FC<Props> = ({ children, className }) => {
  return <div className={classnames(style.heading, className)}>{children}</div>
}
```

こちらの [React.FC 型を拡張する - Qiita](https://qiita.com/Takepepe/items/f66c7e2e1d22b431f148) を参考にさせてもらって、上記問題も無事解決！

ざっくりやり方を説明すると別の型を用意して、declare module に新しい型を追加するだけ！

プロジェクトの任意のディレクトリに `type.ts` を作ってReact.FCに `className` を追加した `FCX` を定義します。

```jsx
declare module 'react' {
  type FCX<P = {}> = FunctionComponent<P & { className?: string }>
}
```

もちろん VFCも同じように作ることができます。

## ComponentのタグをPropsで渡すようにする

ページの見出しで使う用にComponentをh1で作ったはいいけど、ここはh2の方が適切じゃね？ってことありませんか。「あー、コンポーネントのタグ変えられたら楽なのに」ってなりますよね。それ、実はできます。

```jsx
export const Heading: FCX<Props> = ({ children }) => {
  return <h1 className={classnames(style.heading, className)}>{children}</h1>
}
```

propsに渡すことでできます。注意点としては `Component` のように最初を大文字にしてください。というのもReactでコンポーネントを書くときは `<Component></Component>` のように最初の文字は大文字ですよね。それと同じです。
型はもちろん `string` でもいけるんですが、存在しないタグはダメなので、下記のように型ガードさせるようにしましょう。

```jsx
type Props = {
  Component?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
}

export const Heading: FCX<Props> = ({ children, Component = 'h1' }) => {
  return <Component className={classnames(style.heading, className)}>{children}</Component>
}
```

## Function as children

childrenにpropsを渡すことができるって知ってました？例えば下記のようにボタンをクリックしたらinput要素が現れるComponentを作ることもできます。

比較的同じような動作をしてかつ、同一ページに複数存在する可能性があるようなComponentに向いている気がしますね。例えば、モーダルとかドロップダウンメニューとか。

```jsx
type Props = {
  children: (collapsed: boolean, toggleCollapse: () => void) => ReactNode;
};

export const CollapsibleComponent: FC<Props> = ({ children }) => {
  const [collapsed, setCollapsed] = useState(false);

  const toggleCollapse = () => {
    setCollapsed(!collapsed);
  };

  return <div>{children(collapsed, toggleCollapse)}</div>;
};

export const HogeComponent = () => {
	return (
		<div>
			// ・・・
			<CollapsibleComponent>
	      {(collapsed, toggleCollapse) => {
	        if (collapsed) {
	          return (
	            <div>
	              <input type="text" value="React 太郎" />
	              <button type="button" onClick={toggleCollapse}>
	                閉じる
	              </button>
	            </div>
	          );
	        }
	        return (
	          <div>
	            React 太郎
	            <button type="button" onClick={toggleCollapse}>
	              開く
	            </button>
	          </div>
	        );
	      }}
	    </CollapsibleComponent>
			// ・・・
		</div>
	)
}
```

## Componentのジェネリクスの書き方

Componentに対してジェネリクス書けたらいいのにって時ありません？実は書けます。

今回は **render props** を使ったComponentでジェネリクスを使ってみましょう。

render propsは受け取ったpropsを描画に使うことができます。ほとんどFunction as Childrenと同じなんですけどね。以前はApollo Clientでもこのrender propsを押していたような気もします。今はhooks推しですね。

早速ですがReact Queryで取得したデータを `render` に渡して描画させてみます。まず全体像はこちら。

```jsx
type Props<T> = {
  queryKey: string
  render: (queryResult: UseQueryResult<T>) => JSX.Element
}

export const Fetch = <T,>({ render, queryKey }: Props<T>): JSX.Element => {
  const query = useQuery(queryKey, async (): Promise<T> => api.get(queryKey), { enabled: !!queryKey })
  if (query.isLoading) {
    return <h2>Loading...</h2>
  }
  return render(query)
}

export const Home: React.FC = () => {
  return (
    <Fetch<Item[]>
      queryKey="items"
      render={({ data }) => {
        return (
          <div>
            {data?.map((e) => {
              return <div key={e.id}>{e.title}</div>
            })}
          </div>
        )
      }}
    />
  )
}
```

FetchコンポーネントではrenderとqueryKeyを受け取ってrenderにReact Queryで取得したqueryResultを渡しています。あと、 取得するデータによって型が違うので `Fetch<T>` のようにジェネリクスになっていますね。

で、ここで注意点なんですが、Componentでジェネリクスをアロー関数で使いたい場合は `<T,>` のように `,` を入れるようにしてください。それでできます。自分もこれは知らなかった。`, ` をつけていないとReactプロジェクトだと「終了タグどうした！！！」って怒られますww

また、loading中は `Loading...` と表示させて終わったら `return render(queryResult)` としています。

```jsx
type Props<T> = {
  queryKey: string
  render: (queryResult: UseQueryResult<T>) => JSX.Element
}

export const Fetch = <T,>({ render, queryKey }: Props<T>): JSX.Element => {
  const query = useQuery(queryKey, async (): Promise<T> => api.get(queryKey), { enabled: !!queryKey })
  if (query.isLoading) {
    return <h2>Loading...</h2>
  }
  return render(query)
}

// こっちの書き方でもOK!
export function Fetch<T>({ render, queryKey }: Props<T>): JSX.Element {
  const queryResult = useQuery(queryKey, async (): Promise<T> => api.get(queryKey), { enabled: !!queryKey })
  if (queryResult.isLoading) {
    return <h2>Loading...</h2>
  }
  return render(queryResult)
}
```

次にFetchコンポーネントの使用部分をみてみます。まず目に着くのは `<Fetch<Item[]> />` ですよね。なんとも気持ち悪い感じ笑

実はこれがComponentのジェネリクスの書き方なんです。render内で受け取ったqueryResultからdataを取り出せていますね。

```jsx
export const Home: React.FC = () => {
  return (
    <Fetch<Item[]>
      queryKey="items"
      render={({ data }) => {
        return (
          <div>
            {data?.map((e) => {
              return <div key={e.id}>{e.title}</div>
            })}
          </div>
        )
      }}
    />
  )
}
```

hooksの登場であまり活躍の場面がないrender propsですが、hooksと組み合わせることで使える場面は出てきそうですね。というかこのパターンだと毎回 `Loading` しなくていいしめっちゃ良さげです。

また、Componentのジェネリクスも覚えておくと便利ですね。例えば取得したデータを表示させるTableがあったとして、ジェネリクスがあれば汎用的に使えそうです。

## オブジェクトリテラルをComponentに活用する

何でもかんでもif文とかswitch文で出しわけしてませんか？オブジェクトリテラルを活用するとスッキリ宣言的に書くことができますよ。

まずは条件分岐パターンです。よく見るやつですが、typeが追加されると変更箇所が `ACCOUNT_TYPE` と `switch` に追加する必要が出てきますね

```jsx

const SampleComponent = (account) => {
  const { type } = account;

	const ACCOUNT_TYPE = {
	  ADMIN: "ADMIN",
	  OPERATOR: "OPERATOR",
	  VISITOR: "VISITOR",
	};

  switch (type) {
    case ACCOUNT_TYPE.ADMIN:
      return <Admin />;
    case ACCOUNT_TYPE.OPERATOR:
      return <Operator />;
    case ACCOUNT_TYPE.VISITOR:
      return <Visitor />;
    default:
      return null;
  }
};

```

オブジェクトリテラルを使うとこんな感じです。めっちゃスッキリ！！変更箇所もcomponentsに追加するだけ！

```jsx
const SampleComponent = (account) => {
	const {type} = account
	
	const components = {
	  ADMIN: Admin,
	  OPERATOR: Operator,
	  VISITOR: Visitor
	};
	
	const Component = components[type];
	
	return <Component />;
}
```

## おわりに

はじめてのアドカレだったんで色々調べながら記事にしました。ReactのComponentは関数なので JavaScript、TypeScriptでできることはほぼほぼできるんですよね。

また、結構知らないこともあってしかも早速プロジェクトに実践できそうです。