【React】文字の色に合わせたsvgアイコンを表示する

# 背景
Reactでアイコン付きボタンを作っていて、「アイコンの色もボタンに合わせたいなー」ということがありました。
（というより、アイコンにカスタマイズ性を持たせたい）
備忘録として...

# ゴール
以下のようにアイコン付きのボタンを表示させる時に、アイコンの色を文字色と同じように揃えます。

<img width='400px' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/a4422adb-202e-88b9-7d5d-5a0766535846.png'>

<img width='400px' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2508607/05ca5472-a6b1-e98e-d081-54ad9f8122b2.png'>

# 結論
`fill="currentColor"` によって、アイコンの色を親要素で適応されている`color`で塗りつぶす。

# サンプルコード

コンポーネントの作りはざっくり以下のような感じ
- `ButtonWithIcon` というコンポーネント名だが、その実態は`Button`コンポーネントの拡張で、iconがあると、そのアイコンを左側に表示
- `props` として `color` を受け取って、文字色に適用
- `alt` はアクセシビリティ的に必要なので追加

全体コード
```ButtonWithIcon.tsx
import React, { ReactNode } from 'react';

type ButtonWithIconProps = {
  children: ReactNode;
  icon?: 'heart' | 'star';
  color?: string;
  alt?: string;
};

const buttonStyle = {
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  gap: '4px',
  borderRadius: '4px',
  backgroundColor: 'gray',
  paddingTop: '8px',
  paddingBottom: '8px',
  paddingRight: '16px',
  paddingLeft: '16px',
};

export const ButtonWithIcon = (props: ButtonWithIconProps) => {
  const { children, icon, color } = props;
  return (
    <button
      style={{
        ...buttonStyle,
        color,
      }}
    >
      {icon && renderSvgIcon(props)}
      {children}
    </button>
  );
};

function renderSvgIcon(props: ButtonWithIconProps) {
  const svgAlt = getAlt(props);
  const icon = props.icon;

  const svgProps = {
    ...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
    fill: 'currentColor',
    xmlns: 'http://www.w3.org/2000/svg',
    width: '16px',
    height: '16px',
    viewBox: '0 0 512 512',
  };

  switch (icon) {
    case 'heart':
      return (
        <svg {...svgProps}>
          <path
            d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
		C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
		c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
		c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z"
          ></path>
        </svg>
      );
    case 'star':
      return (
        <svg {...svgProps}>
          <polygon points="256,12.531 327.047,183.922 512,198.531 370.938,319.047 414.219,499.469 256,402.563 97.781,499.469 141.063,319.047 0,198.531 184.953,183.922"></polygon>
        </svg>
      );
    default:
      return null;
  }
}

function getAlt({ alt, icon }: ButtonWithIconProps) {
  if (alt) return alt;

  switch (icon) {
    case 'heart':
      return 'いいね';
    case 'star':
      return 'お気に入り';
    default:
      return '';
  }
}
```


# 細かく解説

## svgファイルのレンダリング方法
svgのレンダリングには複数の手法があるので、せっかくなので紹介します。

### Img タグを使用する
もっとも有名で簡単。
この方法は、SVGファイルを直接インポートし、それらを `img` タグの `src` プロパティとして渡します。
```tsx
import star from "./assets/star.svg";
export const Icon= () => {
  return <img src={star} alt="" />;
}
```

この方法では、SVGの幅や高さは変更できても色の変更などができず、スタイルのカスタマイズの柔軟性が制限されます。
この方法は簡単ですが、そのデメリットから非推奨なやり方です。

※Next.jsを使用している場合、'next/image'を使用すると思いますが、これも同様です。

### SVG要素を返すカスタムReactコンポーネント
今回使用した方法です。推し
`create-react-app`でアプリを立ち上げているなら、すでに`SVGR`が組み込まれているため、Reactコンポーネントのように`svg`を使用できます。

```jsx
  const svgProps = {
    ...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
    fill: 'currentColor',
    xmlns: 'http://www.w3.org/2000/svg',
    width: '16px',
    height: '16px',
    viewBox: '0 0 512 512',
  };
  
  return (
    <svg {...svgProps}>
      <polygon points="256,12.531 327.047,183.922 512,198.531 370.938,319.047 414.219,499.469 256,402.563 97.781,499.469 141.063,319.047 0,198.531 184.953,183.922"></polygon>
    </svg>
  );
```

この方法を使用すると、塗りつぶしの色を変更などsvgタグのプロパティに簡単にアクセスできます。
```
<svg fill="#fff" /> 
```
今回は、`button`のカラーと合わせたいので`currentColor`を使用します。

## アクセシビリティな要素
以下の要素です。
```tsx
  const svgProps = {
    ...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
```
**[aria-label](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-label)**
>要素の既定の アクセシブル名 がなかったり、その内容を正確に記述していなかったりして、オブジェクトに意味を与えるために関連付けることができるコンテンツが DOM に表示されていないことがあります。よくある例は、 SVG やアイコンフォント（使用すべきではない）を含む、テキストのないボタンです。

というようにユーザーにボタンの機能を明確に伝えることができるので、svgタグを使用する時につけておく方が良さそう

**[aria-hidden](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-hidden)**
>aria-hidden 属性を使用することで、アクセシビリティ API から操作不可能なコンテンツを隠すことができます。

`alt`がないなら、あえて情報を取得させないようにしておく。

## svgファイルの不要な記述は消しておく
今回サンプルコードのための`svgファイル`は[フリー素材](https://icooon-mono.com/14002-%e3%83%8f%e3%83%bc%e3%83%88%e3%81%ae%e3%83%9e%e3%83%bc%e3%82%af/)を利用しました。

そのまま利用すると、`fill`プロパティなどで色が入ってしまうので、そういった要素を削除します。
ついでに不要なプロパティを削ってデータをスッキリさせましょう。
`svg`は描画時に演算を要するので、データは軽い方が好ましいです。

**before**
```ts
<!--?xml version="1.0" encoding="utf-8"?-->
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->

<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 64px; height: 64px; opacity: 1;" xml:space="preserve">
<style type="text/css">
	.st0{fill:#4B4B4B;}
</style>
<g>
	<path class="st0" d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
		C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
		c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
		c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z" style="fill: rgb(75, 75, 75);"></path>
</g>
</svg>
```
`style、version、id、xmlns:xlink、gタグ` など不要な要素は削除し、他のsvg要素と共通している部分は括り出します。サイズもいい感じに変更しましょう。

今回はないですが、`traslate`で座標を冗長に変更しているファイルを結構見るので、不要な演算も消しましょう。

**after**

```tsx
  const svgProps = {
    ...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
    fill: 'currentColor',
    xmlns: 'http://www.w3.org/2000/svg',
    width: '16px',
    height: '16px',
    viewBox: '0 0 512 512',
  };
        
  <svg {...svgProps}>
      <path
            d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
		C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
		c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
		c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z"
      ></path>
  </svg>
```
かなりスッキリしましたね。

# 最後に
ここまでみていただきありがとうございます！
結論は`fill="currentColor"`というだけなのですが、コンポーネントを作るまでに考えたことも補足で書いていると少し長くなりました。

# 参考
[How to Use SVG in React](https://www.telerik.com/blogs/how-to-use-svg-react)
[aria-label](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-label)
[aria-labelを使ってみよう](https://zenn.dev/moneyforward/articles/b5c9b060cf9237#aria-label%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)
[aria-hidden](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Attributes/aria-hidden)