【React Native】アイコンのスタイル調整のラッパーとしてStyledIconをつくる

# はじめに
react-native-svgでつくったアイコンをスクリーン側で使う際、さらに`View`で囲んでスタイル調整(サイズや位置調整)している場合が多いと思います。
アイコンを呼ぶたびに`View`を書くのは冗長なので、スタイル調整のラッパーとしてStyledIconを作成しました。

# 実装
StyledIconの作成に加えて、その前段階としてアイコン管理も工夫しました。

## アイコンの管理
アプリで使用するアイコン数が多い場合、ジャンルごとにディレクトリをつくります(WarningIconであればalert、HelpIconであればactionなど)。

アイコンをつくったら、各ディレクトリの`index.ts`でサブコンポーネント化します。

```ts:src/components/atoms/icons/action/index.ts
import { CallIcon as Call } from "src/components/atoms/icons/action/CallIcon";
import { CameraIcon as Camera } from "src/components/atoms/icons/action/CameraIcon";
import { HelpIcon as Help } from "src/components/atoms/icons/action/HelpIcon";

export const ActionIcons = {
  Call,
  Camera,
  Help,
};
```

さらに上のディレクトリで、サブコンポーネントを`ICON_LIST`としてまとめます。

```ts:src/components/atoms/icons/index.ts
import { ActionIcons } from "src/components/atoms/icons/action";
import { AlertIcons } from "src/components/atoms/icons/alert";

export const ICON_LIST = {
  ...ActionIcons,
  ...AlertIcons,
};

export { ICON_LIST as Icon };

export type IconNames = keyof typeof ICON_LIST;
```

## アイコンのコンポーネント構成
StyledIconをつくる前に各アイコンのコンポーネントの構成を記載します。
以下のように`color`と`size`をPropsにもちます。

```tsx:src/components/atoms/icons/action/HelpIcon.tsx
import React from "react";
import { G, Mask, Path, Rect, Svg } from "react-native-svg";
import { DEFAULT_COLOR, DEFAULT_SIZE, IconProps } from "src/components/atoms/icons/types";

export const HelpIcon = ({ color = DEFAULT_COLOR, size = DEFAULT_SIZE }: IconProps) => {
  return (
    <Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
      <Mask id="mask0_1858_20478" x="0" y="0" width="24" height="24">
        <Rect width="24" height="24" fill="#C4C4C4" />
      </Mask>
      <G mask="url(#mask0_1858_20478)">
        <Path
          d="M11.95 18C12.3 18 12.596 17.879 12.838 17.637C13.0793 17.3957 13.2 17.1 13.2 16.75C13.2 16.4 13.0793 16.1043 12.838 15.863C12.596 15.621 12.3 15.5 11.95 15.5C11.6 15.5 11.304 15.621 11.062 15.863C10.8207 16.1043 10.7 16.4 10.7 16.75C10.7 17.1 10.8207 17.3957 11.062 17.637C11.304 17.879 11.6 18 11.95 18ZM12.1 7.7C12.5667 7.7 12.9417 7.829 13.225 8.087C13.5083 8.34567 13.65 8.68333 13.65 9.1C13.65 9.38333 13.5543 9.67067 13.363 9.962C13.171 10.254 12.9 10.5583 12.55 10.875C12.05 11.3083 11.6833 11.725 11.45 12.125C11.2167 12.525 11.1 12.925 11.1 13.325C11.1 13.5583 11.1877 13.754 11.363 13.912C11.5377 14.0707 11.7417 14.15 11.975 14.15C12.2083 14.15 12.4167 14.0667 12.6 13.9C12.7833 13.7333 12.9 13.525 12.95 13.275C13 12.9917 13.1127 12.7293 13.288 12.488C13.4627 12.246 13.75 11.9333 14.15 11.55C14.6667 11.0667 15.0293 10.625 15.238 10.225C15.446 9.825 15.55 9.38333 15.55 8.9C15.55 8.05 15.2293 7.354 14.588 6.812C13.946 6.27067 13.1167 6 12.1 6C11.4 6 10.7793 6.13333 10.238 6.4C9.696 6.66667 9.275 7.075 8.975 7.625C8.85833 7.84167 8.81667 8.054 8.85 8.262C8.88333 8.47067 9 8.64167 9.2 8.775C9.41667 8.90833 9.65433 8.95 9.913 8.9C10.171 8.85 10.3833 8.70833 10.55 8.475C10.7333 8.225 10.9543 8.03333 11.213 7.9C11.471 7.76667 11.7667 7.7 12.1 7.7ZM12 22C10.6333 22 9.34167 21.7373 8.125 21.212C6.90833 20.6873 5.846 19.975 4.938 19.075C4.02933 18.175 3.31267 17.1167 2.788 15.9C2.26267 14.6833 2 13.3833 2 12C2 10.6167 2.26267 9.31667 2.788 8.1C3.31267 6.88333 4.02933 5.825 4.938 4.925C5.846 4.025 6.90833 3.31233 8.125 2.787C9.34167 2.26233 10.6333 2 12 2C13.4 2 14.7083 2.26233 15.925 2.787C17.1417 3.31233 18.2 4.025 19.1 4.925C20 5.825 20.7083 6.88333 21.225 8.1C21.7417 9.31667 22 10.6167 22 12C22 13.3833 21.7417 14.6833 21.225 15.9C20.7083 17.1167 20 18.175 19.1 19.075C18.2 19.975 17.1417 20.6873 15.925 21.212C14.7083 21.7373 13.4 22 12 22Z"
          fill={color}
        />
      </G>
    </Svg>
  );
};
```
Propsの型やデフォルト値はアイコン間で共通のため、別ファイルに記載します。

```tsx:src/components/atoms/icons/types.ts
export type IconProps = {
  size?: number;
  color?: string;
  children?: never;
};

export const DEFAULT_COLOR = "#000000";
export const DEFAULT_SIZE = 24;
```

## StyledIconの作成
StyledIconはstyle、color、sizeを制御するアイコンのラッパーとして機能します。

前段階ですべてのアイコンを`ICON_LIST`にひとまとめにしましたが、これはPropsのnameでアイコン名を指定するだけで使えるようにするために行いました。

また、サイズ名をキーとしたオブジェクト`PRESET_ICON_SIZE`をつくることで、Propsでサイズのptを指定する必要がなくなります。

```tsx:src/components/atoms/icons/StyledIcon.tsx
import React from "react";
import { StyleProp, View, ViewStyle } from "react-native";
import { ICON_LIST, IconNames } from "src/components/atoms/icons";

type PresetIconSize = "small" | "medium" | "large";

const PRESET_ICON_SIZE: Record<PresetIconSize, number> = {
  small: 20,
  medium: 24,
  large: 40,
};

type Props = {
  style?: StyleProp<ViewStyle>;
  name: IconNames;
  size?: PresetIconSize;
  color?: string;
};

export const StyledIcon = React.memo(({ style, name, color, size = "medium" }: Props) => {
  const Icon = ICON_LIST[name];
  const _size = PRESET_ICON_SIZE[size];

  return (
    <View style={[{ width: _size, height: _size }, style]}>
      <Icon size={_size} color={color} />
    </View>
  );
});

StyledIcon.displayName = "StyledIcon";
```

StyledIconをつかうときは、以下のように1ラインで記述するだけでOKです。

```tsx
<StyledIcon name="Help" size="large" color="#00FF00" />
```

また、StyledIconをつかわずにIconを呼びたいときは、以下のように記述します。

```tsx
<Icon.Help />
```