Next.JS 프로젝트에서 퍼블리싱을 진행하던 도중, 선배님께 한 가지의 코드 리뷰를 받았다.
바로 svg를 컴포넌트로 만들어서 사용하면 어떻겠냐는 코드 리뷰였다.
이 당시 svg 파일이 조금 많았었는데, 하나하나 바꾸려고 하니 조금 피곤한 작업인 것 같았다.
그렇다면, 컴포넌트를 사용하지 않은 기존 방식에는 어떤 문제가 있을까?
보통은 Next.JS에서 svg를 사용할 때, 파일로 따로 선언해둔 뒤 이를 모듈로
import한 후, 'next/image'의 Image 컴포넌트에 src로 전달하는 식으로 사용한다.
import Image from "next/image"
import Like from "@/assets/like.svg"
const Component = () => {
<Image src={Like} alt="좋아요" />
}
이렇게 되면 코드를 해석할 때, Image로 한 번, src 내에 있는 Like로 한 번,
총 두 번에 걸쳐 코드를 해석해야 하며, 넓게 보았을 때, Image라는 컴포넌트명은
어떻게 보면 조금 비명시적일 수 있다.
여기서 사용된 like.svg는 좋아요 버튼 역할을 하는 작은 svg인데, 이를 Image라고
명시하는 것도 문제가 있고, 이에 다른 css 속성을 주려면 스타일드 컴포넌트를 사용해야 한다는 번거로움이 있다.
말 그대로이다. 스타일을 바꾸려면 svg 모듈 자체를 바꾸어야 한다.
좋아요를 눌렀을 때, 누르지 않았을 때 색깔을 바꾸는 코드를, 다음과 같이 구현할 수 있을 것이다.
import React from "react";
import Image from "next/image"
import UnLike from "@/assets/un_like.svg"
import Like from "@/assets/like.svg"
const Component = () => {
const [isClicked, setIsClicked] = React.useState(false);
return (
<Image src={isClicked ? Like : UnLike} alt="좋아요" />
)
}
이렇게 되면 두 가지의 svg 파일을 불러와야 하며, 해석해야하는 코드의 길이도 늘어나게 된다.
클린 코드를 짜던 입장에서는 기분이 유쾌하지 않을 수 있는 코드이다.
그럼 이런 문제를 어떻게 해결할 수 있을까?
import React from "react";
interface SVGAttributeProps {
width?: number;
height?: number;
color: string;
}
const LikeIcon = ({
width = 20,
height = 20,
color = "#727272"
}: SVGAttributeProps) => {
return (
<svg
width={width}
height={height}
viewBox="0 0 233 201"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M166.918..."
fill={color}
/>
</svg>
);
};
export default LikeIcon;
다음과 같이 나타내고, 이를 메인 컴포넌트에서 사용해보자.
import LikeIcon from "@/assets/LikeIcon"
const Component = () => {
return (
<LikeIcon width={20} height={20} color="red" />
)
}
LikeLogo라는 컴포넌트명을 사용함으로 훨씬 가독성이 좋아지고 명시적인 코드가 된다!
props를 통해서 내가 원하는 svg로 커스텀도 가능하기에, 아까처럼 클릭했을 때
색깔이 바뀌어야하는 경우라면 isClicked를 props로 받아 바꾸어줄 수도 있다!!
import React from "react";
interface SVGAttributeProps {
isClicked: boolean;
}
const LikeIcon = ({
isClicked
}: SVGAttributeProps) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 233 201"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M166.918..."
fill={isClicked ? "red" : "gray"}
/>
</svg>
);
};
export default LikeIcon;
다음과 같이 svg를 선언하고, 컴포넌트에서 사용할 때 props만 바꾸어준다면
훨씬 깔끔하고 훌륭한 코드를 작성할 수 있다!
ArrowIcon.tsx
import React from "react";
interface SVGAttirbuteProps {
direction: "top" | "right" | "bottom" | "left"
}
const path = {
top: "M1.66419 ...",
right: "M1.36271 ...",
bottom: "M40.8592 ...",
left: "M23.1588 ...",
};
const ArrowIcon = ({ direction }: SVGAttribute) => {
return (
<svg
width={width || 41}
height={height || 25}
viewBox="0 0 41 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d={path[direction]} fill="none" />
</svg>
);
};
export default ArrowIcon;
Component.tsx
import ArrowIcon from "@/assets/ArrowIcon"
const Component = () => {
return (
<ArrowIcon direction="bottom" />
)
}
이것은 혁신이다!! 방향대로 새로운 svg 모듈을 만들고 import해 사용하는 것이 아니라,
한 svg에서 상하좌우 네 방향의 path만 따로 객체로 만든 뒤 이를 props로 받아
사용하니 코드가 매우 기분이 좋고 깔끔하다!
이 외에도 여러가지 svg들을 입맛에 맞게 커스텀해 사용할 수 있다!
svg를 컴포넌트로 만드는 좋은 방법에 대해 알아보았다.
선언할 때에 조금의 귀찮음이 있긴 하지만, 이를 공통된 인터페이스나 여러가지 세팅을
통해서 최대한 간소화시켜 클린 코드를 작성하려고 노력해야겠다.
이 글을 쓰면서도 기존에 내가 쓰던 svg 컴포넌트의 코드를 다시 보니
리팩토링할 것들이 보인다..!! 조금 막노동이지만 리팩토링하러 가봐야겠다..!! 화이팅!
유익추