❓ GIFT 프로젝트 진행 중 svg 아이콘들을 어떻게 관리할까 고민을 하게 되었다. 무거운 gif 파일들을 많이 받아오는 사이트 특성 상 성능 최적화를 특히나 더 고려해야하기 때문이다. 성능 최적화와 컴퍼넌트 재사용 문제를 고민하며 개선하고 배워과는 과정을 기록한다.
🚩 svg sprite 기법으로 리액트에서 SvgIcon 컴퍼넌트를 만들어보자.
🌱 SvgIcon 컴퍼넌트 관련 링크
1. SvgIcon 컴퍼넌트: svg sprite html에 임베드 방식
2. SvgIcon 컴퍼넌트 개선: sprite 이미지 라이브러리로 생성 및 이용
svg 스프라이트의 기본 원리는 이미지 스프라이트와 같다. 이미지 스프라이트는 서버와의 통신을 최소화하기 위해 하나의 이미지 파일에 모든 이미지들을 촘촘하게 넣은 후 좌표와 width, height로 필요한 이미지만 잘라내 사용하는 이미지 최적화 기법이다. (스프라이트 이미지 사이즈나 위치가 조금이라도 달라진다면 엄청난 후폭풍이 몰아칠 것...)
SVG(Scalable Vector Graphics)는 백터 그래픽 이미지이며, xml 기반으로 화면에 그려지기 때문에 코드로 조정할 수 있다.
svg sprite 기법은 하나의 svg sheet에 모든 svg를 묶어 서버로부터 단 한번만 전송받은 후 필요한 부분만 뽑아 쓰는 것이다. svg 태그 안에 id로 정의한 이미지가 있다면 이것을 <use>
태그와 href
속성으로 호출하면 된다.
<svg viewBox="0 0 30 10">
<circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="20" fill="white" stroke="red"/>
</svg>
Figma에서 필요한 svg 아이콘들의 묶음을 export했다. 이 때 각각의 아이콘들을 클릭하고 내보내야 각각의 파일들을 받을 수 있다.
피그마로 받은 svg 파일들을 묶은 Sprite Sheet를 만들었다. svg 파일들을 하나의 파일로 묶어주는 유용한 도구로 Spritebot를 사용하면 편하다.
설치하고 실행하면 나오는 위 창에 svg들을 넣고 Save Sprite Sheet만 누르면 된다. 각각의 svg 파일들의 이름이 id가 되므로 파일 이름을 잘 지정해줘야 나중에 편하다.
짠 - svg 스프라이트 코드
root 요소에는 동적으로 랜더링될 요소들을 삽입해 왔기 때문에, 이를 구분해주고자 body 하단에 넣어줬다.
서버로부터 한번만 받아오는 가장 간단한 방법으로는 index.html 파일에 svg 코드를 그냥 정적으로 박아주는 방법이 있다. 하지만 이런 경우 유지 보수에 어려움이 있을 수 있기 때문에 동적으로 삽입했다.
GlobalSvgSprite : html 파일에 정적으로 삽입해주는 컴퍼넌트
- createPortal을 이용해 돔 요소에 접근하여 svg 스프라이트 코드를 넣어준다. 이 컴퍼넌트는 여러번 호출할 일이 없는 App 컴퍼넌트에서 사용했다.
SvgIcon : 필요한 id의 svg 요소를 반환하는 컴퍼넌트
- id는 사용시 정확하게 입력해야하는 부분이기 때문에 입력값들에 대한 타입을 지정해줘야 한다.
-(문제) fill 속성으로 svg의 색 변경이 안된다. 스프라이트 코드에 이미 지정된 색들이 우선권을 갖고 있기 때문이다. (수정이 필요한 부분이니 색 지정은 참고하지 마십시오...)
현재 프로젝트에서는 타입스크립트를 사용하기 때문에 삽입된 svg id 들을 유니온 타입으로 타입 지정하였다. enum을 사용하지 않고 유니온 타입으로 사용한 이유는 런타임에 영향을 주지 않기 위해, 또 귀찮게 import를 하지 않기 위해서다. 카카오 FE 기술 블로그의 아티클을 보고 꿀팁을 전수받았다. 타입스크립트 꿀팁
후훗
- 처음엔 GlobalSvg와 SvgIcon을 분리하지 않고 svg 묶음을 index.html에 넣어주는 작업과 필요한 svg 요소를 반환하는 작업을 하나의 컴퍼넌트에서 모두 수행
이를 위해 SvgIcon 컴퍼넌트 내에서 rendered 라는 상태를 두고 useEffect를 통해 처음 마운트될 때 image들이 삽입되었는지 체크를 했고, 아직 rendered가 안 된 경우만 svg 묶음을 넣어주었다. 이 체크가 완료된 다음 props로 들어온 id에 해당하는 svg 요소를 반환하는 컴퍼넌트를 만들었다.
이렇게 제작한다면 'svg icon'에 대한 모든 관심사가 집중된 점은 좋지만 해당 컴퍼넌트를 사용할 때 마다 불필요한 체크를 한다는 단점이 있었다.
결국 기능적으로 관심사를 분리하여 GlobalSvg 컴퍼넌트로 html 파일에 정적으로 삽입해주는 컴퍼넌트와 필요한 id의 svg 요소를 반환만 하는 SvgIcon 컴퍼넌트를 따로 만들었다.
불필요한 체크는 줄였으나 docs에 GlobalSvg 컴퍼넌트가 SvgIcon 컴퍼넌트에 선행되어야 함을 명시하여야 유지보수에서 발생할 수 있는 문제를 방지할 수 있을 것이다.
- Icon 컴퍼넌트 이름
Icon은 보통 gitignore 기본 세팅으로 추가되어 있는 디렉토리 이름이었다. 이곳저곳에서 자주 사용되는 명칭이니 커스텀 디렉토리 이름을 Icon으로 지정하지 말자...
💡 배운 점
📌 참고 링크
Spritebot
MDN use 참고하기