Reactで最低限のSVGアイコンコンポーネントを作る
追記:2021/08/28 ボタンコンポーネントで、今回作成したアイコンを使用する際にbutton
タグの子要素にdiv
を使用するのはNGのためspan
タグなどを設定してください!
SVGアイコンのコンポーネントを自作する必要があったのでそのメモです。
シンプルなSVGコンポーネント
以下の警告マークのSVGを単体で作る場合は、以下のようなコンポーネントになると思います。
import { VFC } from 'react';
interface Props {
size?: number
fill?: string
}
const AlertIcon: VFC<Props> = props => {
const { size, fill } = props
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
fill={fill}
viewBox="0 0 16 16"><path d="M8.22 1.754a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368L8.22 1.754zm-1.763-.707c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575L6.457 1.047zM9 11a1 1 0 11-2 0 1 1 0 012 0zm-.25-5.25a.75.75 0 00-1.5 0v2.5a.75.75 0 001.5 0v-2.5z"></path>
</svg>
);
};
export default AlertIcon;
使う側は以下のように使います。(本当はstrokeもpropsで渡せた方がいいかも。。)
import AlertIcon from '../AlertIcon'
<AlertIcon size={24} color="#000" />
シンプルなSVGコンポーネントの課題
これで基本OKかと思います。しかし数が多くなってきて、複数のSVGアイコンを1つのコンポーネントで、使いたいケースが出てきたときに、importが増えてちょっと微妙です。またよくあるのがボタンで左または、右などに矢印アイコンがついているようなボタンをよく見かけます。
別でボタンコンポーネントを作る際アイコンが、矢印だけであれば矢印アイコンだけをimportしてくれば良いですが
- 矢印以外にも使いたい
- アイコンなしのボタンもある
みたいな要件だった場合、少々アイコンとの組み合わせが悪くなります。今回はこういうケースを解決するための、最低限のSVGアイコンを作ってみたという記事です。
シンプルなSVGコンポーネントからPropsを削除
まずは、先程のシンプルSVGコンポーネントからPropsを削除しておきます。今回は.tsx
ファイル内にSVGのコードをラップしていますが、propsを渡さなくなったので、ただの.svg
ファイルでも問題ありません。
import { VFC } from 'react';
const AlertIcon: VFC = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"><path d="M8.22 1.754a.25.25 0 00-.44 0L1.698 13.132a.25.25 0 00.22.368h12.164a.25.25 0 00.22-.368L8.22 1.754zm-1.763-.707c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0114.082 15H1.918a1.75 1.75 0 01-1.543-2.575L6.457 1.047zM9 11a1 1 0 11-2 0 1 1 0 012 0zm-.25-5.25a.75.75 0 00-1.5 0v2.5a.75.75 0 001.5 0v-2.5z"></path>
</svg>
);
};
export default AlertIcon;
Icon.tsxを作成して全てのアイコンをimportしてtypeをもたせる
先程のAlertアイコンの他に、ArrowアイコンやUserアイコンなど複数のSVGがあると仮定して全てimportしてきます。またtypeというpropsを用意してアイコンの切り替えができるようにします。他にもアイコンをクリックしたらイベントが発火できるようにonClickも追加しておきます。(何故HTMLDivElementなのかは後でわかります。)
import AlertIcon from './Alert';
import ArrowIcon from './ArrowIcon';
import UserIcon from './UserIcon';
export const IconType = {
AlertIcon,
ArrowIcon,
UserIcon
};
interface IconProps {
size?: number;
type: keyof typeof IconType;
fill?: string;
onClick?: React.MouseEventHandler<HTMLDivElement>;
}
Iconコンポーネントをラップする
まず先程設定したIconType
を以下のようにコンポーネントとして使えるように設定します。この定数の部分がsvgタグになります。
const IconSvgFile = IconType[type];
JSXとスタイル部分を書いていきます。先程のIconSvgFileをdiv要素などでラップします。スタイリングもしつつラップした部分にpropsを設定します。
先に完成形を以下に貼ります。CSSは@emotion/styled
を使っています。
import { VFC } from 'react';
import styled from '@emotion/styled';
import AlertIcon from './Alert';
import ArrowIcon from '.ArrowIcon';
import UserIcon from '.UserIcon';
export const IconType = {
AlertIcon,
ArrowIcon,
UserIcon
};
interface IconProps {
size?: number;
type: keyof typeof IconType;
fill?: string;
onClick?: React.MouseEventHandler<HTMLDivElement>;
}
export const Icon: VFC<IconProps> = (props) => {
const { size , type, onClick, fill } = props;
const IconSvgFile = IconType[type];
return (
<IconWrapper size={size} onClick={onClick} fill={fill}>
<IconSvgFile />
</IconWrapper>
);
};
const IconWrapper = styled.div<{ size: number; fill?: string;}>`
width: ${({ size }) => size}px;
height: ${({ size }) => size}px;
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'inherit')};
fill: ${({ fill }) => (fill ? fill : '')};
`;
全てのpropsをdivに渡すことでSVGのサイズと色を制御しています。ちょっと荒業っぽいですがこれで使いやすい感じになりました。
<Icon type="FileIcon" size={24} fill="#f00" />
後でボタンコンポーネントを作る際に今回作ったtype
をそのまま渡せばアイコンありなしボタンが簡単に作成できそうです。
まとめ
- SVGをdiv要素などでラップしたコンポーネントを作って全SVGアイコンをimportする
- 複数コンポーネントをpropsで切り替えできるように設定する
- アイコン付きボタンコンポーネントを作る際も同じpropsを渡せば簡単に実装できる
他にもいい方法があれば是非教えてください!!!!