Leon Chaewon Kong's dev blog

React에서 SVG 요리하기

React에서 SVG를 사용하는 방법은? 사실 간단합니다.

import React from "react";

const ImageList = () => (
<div>
  <ul>
    <li>
      <img src="[path-to-svg-icon]/desktop.svg" />
    </li>
      <img src="[path-to-svg-icon]/tablet.svg" />
    <li>
      <img src="[path-to-svg-icon]/mobile.svg" />
    </li>
  </ul>
<div>
);

보통 이런식으로 사용하죠.

여기엔 하나의 문제가 있습니다. 모든 아이콘이 전부 desktop/tablet/mobile 총 3가지 옵션이 있다고 생각해 보세요. 아이콘의 개수는 3배가 됩니다. 관리하기 어렵고 불필요하게 많습니다.

이 경우, 같은 아이콘이라면 desktop/tablet/mobile을 하나의 파일로 합쳐 관리하는 것이 편합니다.

<!-- dog_desktop.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <!-- all the paths and shapes for desktop.svg -->
</svg>

<!-- dog_tablet.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <!-- all the paths and shapes for tablet.svg -->
</svg>

<!-- dog_mobile.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
  <!-- all the paths and shapes for mobile.svg -->
</svg>

이런 식으로 같은 아이콘이 desktop/tablet/mobile 별로 나눠져 있다고 해보겠습니다. 이 경우, 다음처럼 합칠 수 있습니다.

dog.svg

<svg xmlns="http://www.w3.org/2000/svg">
  <symbol id="desktop" viewBox="0 0 24 24">
    <!-- all the paths and shapes for desktop.svg -->
  </symbol>
  <symbol id="tablet" viewBox="0 0 16 16">
    <!-- all the paths and shapes for tablet.svg -->
  </svg>
  <symbol id="mobile" viewBox="0 0 8 8">
    <!-- all the paths and shapes for mobile.svg -->
  </svg>
</svg>

자, 이제 React에서 사용하는 문제가 남았네요. React에서는 기본적으로 아래처럼 사용할 수 있습니다.

import React from "react";

const ImageList = () => (
<div>
  <ul>
    <li>
      <img src="[path-to-svg-icon]/dog.svg#desktop" />
    </li>
      <img src="[path-to-svg-icon]/dog.svg#tablet" />
    <li>
      <img src="[path-to-svg-icon]/dog.svg#mobile" />
    </li>
  </ul>
<div>
);

desktop/tablet/mobile은 아이콘의 path에 “#”을 붙여 구분할 수 있습니다. dog.svg#desktop 이런 식으로요.

하지만 이런 식의 사용 방법은 두 가지 문제가 있습니다.

  1. path를 string으로 고정하여 사용해서 실제 asset의 path가 변경될 경우, 코드도 함께 변경해 줘야 합니다.
  2. 사용할 때마다 매번 asset의 path를 하나하나 세며 확인하고 입력해줘야 합니다.

예를들어, 제가 만든 <ImageList /> 컴포넌트는 src/components/Main/List/에 있고, dog.svgsrc/assets/icons/에 있다고 한다면, 제가 src로 입력해야 하는 경로는 ../../../assets/icons/dog.svg가 됩니다. 솔직히 정확한 경로를 입력한다는 것이 ../가 반복될수록, 즉 디렉토리가 깊을수록 참 힘들고 헷갈립니다.

그래서 간단하게 다음과 같은 해결책을 제안합니다.

[path-to-svg-icon]/index.tsx

import React from "react";
const Icons = require("./dog.svg");

interface Props {
  size: number;
  media: string;
  className: string;
}

export const Icon = ({ size, media, className }: Props) => (
  <svg className={className} width={size} height={size}>
    <use xlinkHref={`${Icons}#${media}`} />
  </svg>
);

즉, dog.svg가 있는 디렉토리 안에 index.tsx 파일을 하나 생성하고, 거기서 <Icon /> 컴포넌트를 만들어 제공하는 겁니다. 이렇게 사용하면 index.tsxdog.svg와 같은 디렉토리에만 있는 한, dog.svg가 속한 디렉토리가 src/assets/icons에서 src/icons로 변경된다 하더라도 문제가 발생하지 않습니다. 또, 복잡한 상대경로를 입력하지 않아도 되죠.

참고자료