개발자
 류준열
Icon 컴포넌트 개발기
assets을 관리하는 체계가 없다보니, 중복 생성된 svg들이 있었다. (delete.svg, trash.svg, remove.svg ,....)
이를 해결하기 위해 피그마의 정보를 이용한 Icon 컴포넌트를 만들었다.
(dynamic import를 사용한 방식도 있는데, 이는 태생적으로 로딩이 발생할 수 밖에 없는 구조라 보고 넘겼다.)
기존 문제점
기존에는 SVG 파일을 assets 폴더에 저장한 뒤, 아래와 같이 직접 import해서 사용했다.
import Search from "@/../public/assets/icon/search.svg"; ... <InputWrapper> <Input /> <Icon component = {Search} /> </InputWrapper>
이렇게 사용하면 중복 svg가 이름만 다른채로 존재하는 문제가 있었다. (ex: search.svg, search_caution.svg)
개선 방향 및 설계 의도
우리 팀은 디자인 시스템에 등록된 아이콘만 사용하기로 정했고, 해당 아이콘들은 모두 Figma에 정의되어 있었다. 이에 따라 아이콘 사용을 아래와 같이 단순화하고자 했다
- 아이콘 이름만 알면 사용 가능하도록 props 기반 접근
- 중복 저장 방지를 위해 아이콘 데이터를 코드로 중앙 관리
먼저, Figma에 등록된 아이콘 이름을 기반으로 한 ICONS 객체를 정의하고, 각 아이콘의 path를 문자열로 저장했다.
export const ICONS = { Setting: "M7.99204 ... 11.8746 5.59249 10.001Z", Info: "M12 8.72368C11.5858 ... 6.47715 22 12 22Z", Minus: "M2.25 12C2.25 11.5858 ... 11.5858 2.25 12 2.25Z", Close: "M4.46967 4.46967C4.76256 ... 4.17678 4.76256 4.46967 4.46967Z", Refresh: "M15.674 5.23438C13.04 ... 1.96683 8.44187 2.34775 8.27916L4.6468 7.29716Z", Download: "M12 3.25C12.4142 3.25 ... 14.7523 3.47654 14.3693 3.88594 14.3063Z", Upload: "M12 3.25C12.2052 3.25 ... 3.88594 14.3063Z", Delete: "M9.75 4.5C9.75 3.5335 ... 13.5858 8.75 14 8.75Z", };
(동료: 좀 무식한 방법이지만 깔끔한데요..?)
SVG의 path만 추출해서 저장한 이유는 다음과 같다
- SVG 전체 태그를 보관하는 것보다 훨씬 경량화되며,
- viewBox, fill, width, height 등의 속성을 런타임에서 커스터마이징할 수 있어 재사용성이 높아진다.
Icon 컴포넌트 구현
이제 아이콘을 이름 기반으로 불러올 수 있는 Icon 컴포넌트를 만들었다.
핵심 포인트는 다음과 같다:
- icon prop으로 문자열 이름을 받고, 해당 이름에 해당하는 path를 ICONS 객체에서 찾는다. (유니언 타입 지정)
- 필요에 따라 크기(size), 색상(color), 회전(rotate), 스핀(spin) 등의 속성을 함께 커스터마이징할 수 있다.
- 디자인 시스템에서 지정한 기본 사이즈와 색상을 default로 지정해 일관성 유지
import * as React from "react";
import { ICONS } from "./constants";
export interface IconBaseProps extends Omit<React.HTMLProps<HTMLSpanElement>, "width" | "height"> {
  spin?: boolean;
  rotate?: number;
}
export type IconProps = keyof typeof ICONS;
export interface CustomIconComponentProps {
  size?: string | number;
  fill?: string;
  viewBox?: string;
  className?: string;
}
export interface IconComponentProps extends IconBaseProps {
  viewBox?: string;
  component?:
    | React.ComponentType<CustomIconComponentProps | React.SVGProps<SVGSVGElement>>
    | React.ForwardRefExoticComponent<CustomIconComponentProps>;
  ariaLabel?: React.AriaAttributes["aria-label"];
  color?: string;
  icon: IconProps; // 피그마의 assets명을 유니언 타입으로 관리
}
// eslint-disable-next-line react/display-name
export const Icon = React.forwardRef<HTMLSpanElement, IconComponentProps>((props, ref) => {
  const { icon, size: propSize, spin, rotate, className, color: propColor, ...restProps } = props;
  const size = propSize || "24px";
  const fill = propColor || "var(--Grey-G-10)";
  const iconClassName = `${className || ""} ${spin ? "icon-spin" : ""}`;
  const path = ICONS[icon];
  return (
    <span ref={ref} className={iconClassName} {...restProps}>
      <svg
        width={size}
        height={size}
        viewBox={`0 0 24 24`} // 피그마에 등록된 svg의 width, height가 24,24임
        fill={fill}
        fillRule="evenodd"
        clipRule="evenodd"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path d={path} />
      </svg>
    </span>
  );
});
효과
이제는 피그마에 등록된 이름만 알면 다음과 같이 간단히 사용할 수 있다.
<Icon icon = "Refresh" size = {32} color = "#2D8CFF" />
이 방식을 이용하면서 svg를 관리하는 노력을 들이지 않아도 되게 되었고, 유지보수성, 가독성이 개선되었다.
