NavBar나 좋아요 버튼 등 각종 아이콘에는 SVG 형식의 이미지를 활용했다. SVG가 확장 가능한 벡터 기반 그래픽으로 확대, 축소에 관계 없이 이미지를 선명하게 볼 수 있을 뿐 아니라 용량이 가볍고 또 코드로 이루어져 있어 CSS를 활용해 수정할 수 있기 때문이다.
위와 같이 NavBar에서 아이콘을 클릭해 페이지에 이동하게 되면 해당하는 아이콘에 색이 들어오게끔 구현하고 싶었다.
img srcimport Home from 'assets/icon-home.svg'
<img src={Home} />
처음 시도는 img로 넣는 방식이었다. 흔히 사용하는 방식이지만 SVG 파일의 장점을 활용하지 못 하는 방식이기도 하다. 클릭 시 아이콘에 색을 채우는 등 변화를 주기 위해서 CSS를 활용하지 못 하고 이미지 파일 자체를 교체하는 방식으로 구현해야 하는데, SVG을 100% 활용하지 못 하는 방식이기에 아쉬움이 남았다.
ReactComponent로 💫import { ReactComponent as Home } from 'assets/icon-home.svg'
<Home />
SVG 자체를 React 컴포넌트로 만들 수 있는 방법이 있다. 이 방식은 Create React App으로 React 프로젝트를 생성할 경우 사용할 수 있다!
이번 프로젝트의 경우 SVG를 활용한 아이콘의 색이 변하는 기능을 구현해야 했기에 styled-components와 엮어서 SVG를 변화 시킬 수 있도록 컴포넌트화 하는 것이 더 효율적이었다.
잠시 styled-components를 사용한 이유를 알아보자!
React에서도 물론 일반적인 CSS를 사용할 수 있다. module.css를 사용하면 컴포넌트 단위로 CSS의 스코프를 가져갈 수도 있다. 그럼에도 CSS-in-JS 방식을 택한 것은 같은 파일 내에서 컴포넌트의 스타일링과 로직을 작성할 수 있어 편리하며 동적으로 스타일링을 할 수 있다는 특징을 가지기 때문이었다. 물론 스타일링이 길어지다보니 파일을 분리하는 것이 좋은가 고민하는 순간은 있었지만, 상태나 속성에 따라 스타일링을 변경할 수 있다는 장점이 크기 때문에 CSS-in-JS 방식 중 하나인 styled-components를 사용한 것은 후회없는 선택이었다.

좋아요 버튼 클릭 시 아이콘에 메인 색상이 채워지도록 구현하는 과정을 살펴보자
<svg width="20" height="20" viewBox="0 0 20 20"
fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M16.9202 4.01322C16.5204 3.60554 16.0456 3.28215
15.5231 3.0615C15.0006 2.84086 14.4406 2.72729 13.875
2.72729C13.3094 2.72729 12.7494 2.84086 12.2268
3.0615C11.7043 3.28215 11.2296 3.60554 10.8298
4.01322L9.99997 4.85889L9.17017 4.01322C8.36252 3.19013
7.26713 2.72772 6.12495 2.72772C4.98277 2.72772 3.88737
3.19013 3.07973 4.01322C2.27209 4.83631 1.81836 5.95266
1.81836 7.11668C1.81836 8.28071 2.27209 9.39706 3.07973
10.2201L3.90953 11.0658L9.99997 17.2728L16.0904
11.0658L16.9202 10.2201C17.3202 9.81266 17.6376 9.32885
17.8541 8.79635C18.0706 8.26385 18.182 7.69309 18.182
7.11668C18.182 6.54028 18.0706 5.96952 17.8541
5.43702C17.6376 4.90452 17.3202 4.4207 16.9202 4.01322Z"
stroke="current" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
바꾸고자하는 property에 current 값을 주면 해당하는 property는 props를 통해 사용자가 값을 변경할 수 있게 된다. fill과 stroke에 current로 값이 들어간 것을 확인할 수 있다.
const StyledHeartIcon = styled(HeartIcon)`
stroke: ${({ isLike }) =>
isLike ? 'var(--main-color)' : 'var(--sub-text-color)'};
fill: ${({ isLike }) => (isLike ? 'var(--main-color)' : 'transparent')};
`;
export default function PostCard() {
const [isLike, setIsLike] = useState(false);
return (
...
<IconButton>
<StyledHeartIcon onClick={() => setLike(!isLike)} isLike={isLike} />
</IconButton>
...
);
}
그런데..

처음에는 isLike에 대문자가 들어간 것이 문제인 줄 알았다. DOM element에 관한 내용은 잘 알지 못 하는 부분이라 넘어가고 spell it as lowercase에만 집중했다. 그래서 like로 변수명을 바꿔봤지만
const StyledHeartIcon = styled(HeartIcon)`
stroke: ${({ like }) =>
like ? 'var(--main-color)' : 'var(--sub-text-color)'};
fill: ${({ like }) => (like ? 'var(--main-color)' : 'transparent')};
`;
export default function PostCard() {
const [like, setLike] = useState(false);
return (
...
<IconButton>
<StyledHeartIcon onClick={() => setLike(!like)} like={like} />
</IconButton>
...
);
}

Warning: Received
falsefor a non-boolean attributeliked.
여전히 에러가 계속됐다. 이번엔 like 속성을 boolean 타입으로 인식하지 못 한 것이 문제라며 명확하게 원인을 짚어줬다. 근본적인 원인은 like 속성이 DOM에 그대로 전달된 것이었다. 렌더링 하려는 목적이 아니었기 때문에 string으로 변환하는 것은 의미가 없다고 생각했다. 따라서 DOM에 그려지는 것을 막기 위한 방법이 필요했고 답은 공식문서에서 찾을 수 있었다!
https://styled-components.com/docs/api#transient-props
문제를 해결하기 위해서는 props앞에 $를 붙여주면 된다. $를 붙이면 props를 임시 props로 만들어 DOM 요소로 렌더링되지 않도록 한다.
const StyledHeartIcon = styled(HeartIcon)`
stroke: ${({ $like }) =>
$like ? 'var(--main-color)' : 'var(--sub-text-color)'};
fill: ${({ $like }) => ($like ? 'var(--main-color)' : 'transparent')};
`;
export default function PostCard() {
const [like, setLike] = useState(false);
return (
...
<IconButton>
<StyledHeartIcon onClick={() => setLike(!like)} $like={like} />
</IconButton>
...
);
}
단순 UI 구현 중 발생한 에러를 처리하는 과정이었기 때문에 like 값을 가져오는 로직은 수정되어 위 코드와는 차이가 있다.
SVG를 React 컴포넌트화 하는 방법은 Create React App을 통해 React 프로젝트를 생성할 때 사용할 수 있는 방법이다. 그렇기 때문에 요즘 많이 사용하는 Vite, Next.js 등을 통해 프로젝트를 생성하는 경우에는 (아마도) 적용되지 않을 것이다. 앞으로 활용하게 될테니 이 부분을 염두에 두고 다른 방법도 생각해봐야 할 것이다.🫡
https://create-react-app.dev/docs/adding-images-fonts-and-files/#adding-svgs
https://styled-components.com/docs/api#transient-props