[svgr] SVG 아이콘 컴포넌트 생성 :: 마이구미
이 글은 svg 아이콘과 관련된 라이브러리 SVGR 을 다룬다.
리액트에서 아이콘을 svg 파일로 관리하는 경우 도움을 줄 수 있는 라이브러리이다.
글에서 소개하는 예제들은 각 tag 를 checkout 을 통해 확인할 수 있다.
예제 - https://github.com/hotehrud/svgr-example
조금이나마 도움을 줄 수 있는 라이브러리들을 소개하는 카테고리로 분류된 글이다.
알아두면 좋은 라이브러리
SVGR 은 svg 파일을 React 컴포넌트로 변환시켜주는 라이브러리이다.
원본 svg 와 svg 를 React 컴포넌트로 변환한 코드를 각각 확인해보자.
// icon.svg
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" xml:space="preserve">
<path d="M22,11h-4.2L21,7.8l-1.4-1.4L15,11h-2V9l4.7-4.7l-1.4-1.4L13,6.2V2h-2v4.2L7.8,2.9L6.3,4.3L11,9v2H9L4.3,6.3
L2.9,7.8L6.2,11H2v2h4.2L3,16.2l1.4,1.4L9,13h2v2l-4.7,4.7l1.4,1.4l3.3-3.3V22h2v-4.2l3.2,3.2l1.4-1.4L13,15v-2h2l4.7,4.7l1.4-1.4
L17.8,13H22V11z"/>
</svg>
// Icon.tsx
import * as React from "react";
const SvgAc = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
xmlSpace="preserve"
{...props}
>
<path d="M22 11h-4.2L21 7.8l-1.4-1.4L15 11h-2V9l4.7-4.7-1.4-1.4L13 6.2V2h-2v4.2L7.8 2.9 6.3 4.3 11 9v2H9L4.3 6.3 2.9 7.8 6.2 11H2v2h4.2L3 16.2l1.4 1.4L9 13h2v2l-4.7 4.7 1.4 1.4 3.3-3.3V22h2v-4.2l3.2 3.2 1.4-1.4L13 15v-2h2l4.7 4.7 1.4-1.4-3.3-3.3H22v-2z" />
</svg>
);
export default SvgAc;
svg 파일은 svg 태그로 구성되어있고, React 컴포넌트는 랜더링을 svg 태그로 구성하고 있는 모습을 볼 수 있다.
그렇다면 왜 svg 를 React 컴포넌트로 변환해야하는가?
svg 를 활용해서 아이콘을 관리 및 사용하거나 아이콘 라이브러리를 쓰면서 커스텀 아이콘이 필요한 경우가 있다.
이러한 경우에는 <img src="icon.svg" /> 가 아닌 <Icon /> 형태를 더 선호할 것이다.
const Car = () => {
return (
<svg ... />
)
}
const App = () => {
return (
<div>
<Car />
<div>
)
}
결국 우리는 React 컴포넌트를 작성하고 svg 태그를 입력하는 수동적인 작업이 필요한 것이다.
이러한 수동적인 작업을 svgr 에서 대신 해주는 것이다.
svg 확장자 파일을 jsx 확장자 파일로 생성해주는 것이다.
- svg/car.svg => components/Ac.jsx
- svg/airplane.svg => components/Airplane.jsx
- svg/heart.svg => components/Heart.jsx
원하는 아이콘 파일(svg)을 svg 디렉토리에 넣은 후, svgr 명령어를 실행해주면 된다.
$ svgr svg -d src/components
svg 디렉토리에 있는 svg 파일들을 기반으로 components 디렉토리에 React 컴포넌트가 생성된다.
그리고 생성된 컴포넌트들을 export 하는 index 파일까지 생성된다.
export { default as Ac } from "./Car";
export { default as Accessible } from "./Airplane";
위의 예제는 태그 01-basic 를 통해 확인할 수 있다.
그리고 svgr 을 활용해서 더 많은 기능들이 가능해진다.
여기서는 2가지 기능을 추가해본다.
- svg title 요소 넣기
- svg 를 emotion/styled 로 감싸기
우선 svg title 기능을 넣어보자.
svg 에서는 title 요소를 제공해준다. (https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title)
title 의 값이 마우스오버를 하면 툴팁처럼 보여지게 된다.
사전에 svg 파일에서 title 요소를 넣을 수도 있겠지만, title 의 값을 props 로 전달 받아야한다면 어떻게 할 수 있을까?
svgr 을 통해 생성되는 React 컴포넌트를 수정해야한다.
props 에 title 을 추가해야하고 랜더링 코드쪽에서는 <title>{props.title}</title> 같은 코드가 존재해야한다.
const SvgAirplane = ({ title, ...props }: SVGProps<SVGSVGElement> & {
title: string,
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
{...props}
>
{!!title && <title>{title}</title>}
<path d="m6.568 4.675 6.302 2.76-2.228 2.326-4.78-4.224a.25.25 0 0 1-.042-.387l.44-.44a.251.251 0 0 1 .308-.035Zm10.403 6.847 2.76 6.315a.252.252 0 0 1-.036.309l-.44.439a.25.25 0 0 1-.387-.042l-4.23-4.8 2.333-2.221ZM6.84 15.22l-2.692-2.262a.25.25 0 0 1-.074-.404l.56-.56a.25.25 0 0 1 .31-.034l3.147.82-1.16 2.249c-.028.056-.064.12-.091.19Zm4.794 1.102.894 3.183c.024.087 0 .18-.064.245l-.598.596a.25.25 0 0 1-.404-.072l-2.275-2.707.17-.085 2.277-1.16Zm8.426-12.025.041.044.017.02c.22.273.25.64.266.864.074 1.216-.075 2.074-.47 2.698l-.036.06a1.713 1.713 0 0 1-.327.407l-7.48 7.127-.054.051-.065.033-2.817 1.437c-.346.188-1.266.442-1.737-.03-.51-.509-.192-1.438-.028-1.748l1.435-2.807.033-.065.05-.052 7.125-7.479c.303-.322.633-.48.951-.606.637-.24 1.447-.307 2.566-.205a.898.898 0 0 1 .53.251Zm-1.172 7.585a.248.248 0 0 1 .354 0l1.06 1.061a.247.247 0 0 1 0 .353l-1.385 1.386-.87-1.959.841-.84ZM11.11 4.104a.248.248 0 0 1 .354 0l1.06 1.061a.247.247 0 0 1 0 .353l-.842.841-1.958-.869 1.386-1.386Z" />
</svg>
);
};
이것이 의미하는 것은 svgr 에서 자동으로 변환해주는 템플릿을 우리가 직접 커스텀 할 수 있어야하는 것이다.
이것을 위해 svgr 에서는 Custom Template 기능을 제공해주고 있다.
const template = (variables, { tpl }) => {
return tpl`
${variables.imports};
${variables.interfaces};
const ${variables.componentName} = (${variables.props}) => (
${variables.jsx}
);
${variables.exports};
`
}
module.exports = template
svgr.config.js 파일을 생성해서 기능에 대한 커스텀 및 확장을 할 수 있다.
const template = ({ componentName, jsx, props, imports, exports }, { tpl }) =>
tpl` ... `;
module.exports = {
template,
jsx: {
babelConfig: {
plugins: [dynamicTitlePlugin],
},
}
};
템플릿을 우리가 원하는 형태로 작성하고, 랜더링하는 부분에 title 태그를 삽입하는 플러그인을 선언해주었다. (예제 코드 참고)
svg title 기능이 적용된 예제 코드는 태그 02-svg-title 를 통해 확인할 수 있다.
이처럼 커스텀이 자유롭다면 조금 더 나아가서 emotion/styled 로 랩핑하여 style Props 를 활용할 수 있다.
이것을 통해 얻는 이점은 일반적인 svg 태그가 아닌 Styled Component 처럼 사용하는 것이다.
그렇게 되면, svg 태그만이 가질 수 있는 속성 이외의 theme 과 같은 style Props 들을 이용할 수 있게 된다.
<Car color="primary" />
위처럼 사용하기 위해서는 몇가지 아이디어가 필요하다.
우선 Styled Component 로 제작된 컴포넌트(Svg.tsx)를 하나 더 생성해준다.
const color = (props) => {
if (props.color) {
return css`
color: props.theme[props.color].base;
`
}
return null
}
const Svg = styled('svg')`
flex: none;
line-height: 1;
${color}
`
export default Svg
그리고 svgr 을 통해 변환된 React 컴포넌트의 <svg ... /> 코드를 <Svg ... /> 로 변경해주어야한다.
이것을 위한 플러그인을 추가해준다. (예제 코드 참고)
// svgr.config.js
module.exports = {
template,
jsx: {
babelConfig: {
plugins: [dynamicTitlePlugin, replaceSvgPlugin],
},
}
};
결과적으로 변환된 React 컴포넌트의 랜더링 부분은 svg 태그가 아닌 Styled Component 인 Svg 로 대체되는 것이다.
styled Compoent 가 적용된 예제는 태그 03-svg-emotion 를 통해 확인할 수 있다.
사실 CRA(Create-react-app) 를 사용해봤다면, 우리는 다음과 같은 코드를 볼 수 있다.
import { ReactComponent as Logo } from './logo.svg';
function App() {
return (
<div>
{/* Logo is an actual React component */}
<Logo />
</div>
);
}
위 코드는 CRA 설치 시 기본적으로 존재하는 코드이다.
svg 파일을 import 했는데 React 컴포넌트처럼 바로 사용하는 모습을 볼 수 있다.
이것이 가능한 이유는 이미 CRA 내부에 기본적으로 svgr 라이브러리를 지원하고 있기 때문이다.
https://create-react-app.dev/docs/adding-images-fonts-and-files/#adding-svgs
실제로는 priceline 디자인 시스템의 아이콘 모듈을 참고하였고, 본인의 예제는 간략하게 정리한 것이다.
더 세세한 코드를 원하면 참고하면 좋을 것 같다.