Material Icon 컴포넌트 만들기

김동현·2023년 8월 10일
0

material icon 로드

  • html의 link 엘리먼트로 로드하기
  • CSS의 @import 로 로드하기
  • Node.js 패키지로 다운받은후 import 하기

첫 번째와 두 번째 방법은 다른 사이트에 호스팅된 외부 CSS 파일을 가져오므로 네트워크 속도에 영향을 받을 수 있다.
따라서 대부분 웹 애플리케이션은 Node.js 패키지 형태로 구현된 CSS 프레임워크를 내장하는 형태로 배포한다.

<span className="material-icons">home</span>
<span className="material-icons">search</span>

Icon 사용자 컴포넌트 구현

<span className="material-icons">home</span>

위의 형태는 다음처럼 사용하는 것이 더 간결하다.

<Icon name="material-icons" />

고쳐보자.

type IconProps = {
  name: string;
}
const Icon: FC<IconProps> = ({name}) => (
  <span className="material-icons">{name}</span>
)

Icon 컴포넌트의 props으로 name 만 전달할 수 있다.
만약 style을 props로 넘겨주려면 다음과 같이 한다.

<Icon name="material-icons" style={{color:'blue'}}/>
type IconProps = {
  name: string;
  style?: CSSProperties
}
const Icon: FC<IconProps> = ({name, style}) => (
  <span className="material-icons" style={style}>{name}</span>
)

Icon 컴포넌트 개선하기

속성을 추가할 때마다 컴포넌트를 수정하는 것은 비효율적이다.
예를 들어 다음과 같다.

<Icon name="material-icons" style={{color:'blue'}} prop1="value1" prop2="value2"/>
type IconProps = {
  name: string;
  style?: CSSProperties
  prop1: string;
  prop2: string;
}

const Icon: FC<IconProps> = ({name, style}) => (
  <span className="material-icons" style={style} prop1={prop1} prop2={prop2}>{name}</span>
)

매개변수에 rest operator를 사용하고 JSX의 {...props} 구문으로 속성들을 생략할 수 있다.

type IconProps = {
  name: string;
  style?: CSSProperties
  prop1: string;
  prop2: string;
}

const Icon: FC<IconProps> = ({name, ...props}) => (
  <span className="material-icons" {...props}>{name}</span>
)

CSS 클래스 선택자를 이용한 컴포넌트

CSS에 다음과 같이 정의되어 있다고 가정하자.

.text-blue {color: blue}

그리고 다음과 같이 컴포넌트를 호출하고 싶다.

<Icon name="material-icons" className="text-blue"/>

별 생각없이 만든다면 다음과 같이 만들 수 있다.

type IconProps = {
  name: string
  style?: CSSProperties
  className?: string
}

const Icon: FC<IconProps> = ({name, className: _className, ...props}) => {
  const className = ['material-icons', _className].join(' ')
  return (
    <span className={className} {...props}>
      {name}
    </span>
  )
}

물론 동작한다.
하지만, className과 같은 속성은 리액트 컴포넌트에서도 지원하는 속성이다.
autoFocus , hidden , placeholder 등...필요한 속성들을 사용할 때 일일이 하나씩 추가하는것 보다는 기존의 리액트 컴포넌트에서 지원하는 속성들을 한번에 포함시키는 것이 유지보수면에서 훨씬 낫다.

type ReactSpanProps = React.DetailedHTMLProps<
  React.HTMLAttributes<HTMLSpanElement>,
  HTMLSpanElement
>

type IconProps = ReactSpanProps & {
  name: string
}

const Icon: FC<IconProps> = ({name, className: _className, ...props}) => {
  const className = ['material-icons', _className].join(' ')
  return (
    <span className={className} {...props}>
      {name}
    </span>
  )
}

style 속성도 원래 지원하는 속성이기 때문에 지울 수 있다.
임의로 추가한 name 속성만 신경쓰면 된다.

로딩 이슈

하지만 위의 방식은 어디까지나 컴포넌트를 만들기 위해 보여준 예시이다.
위의 코드를 실행하면 외부 사이트에 호스팅된 아이콘 폰트를 로드하는 방식이나 Node.js 패키지 형태로 구현된 CSS 프레임워크를 내장하는 형태로 배포하는 방식이나 둘 다 깜빡임 현상이 있다.
글자가 잠깐 보였다가 아이콘으로 바뀐다.

해결책으로는 다음의 방법이 있다.

1. 미리 로드

<head> 태그 안에 다음 코드를 추가하여 웹폰트를 미리 로드할 수 있다.
이렇게 하면 웹폰트 로드 시간이 줄어들며, 글자가 보이는 현상도 줄어든다.

<link
  rel="preload"
  href="https://fonts.gstatic.com/s/materialicons/v114/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

2. font-display

위의 CSS role을 통해 웹폰트 로드 시 글자가 보이지 않도록 설정할 수 있다.
글자가 한 번에 아이콘으로 전환되기 때문에 덜 튀어 보인다.

.material-icons {
  font-display: block;
}

3. SVG 사용하기

그냥 아이콘은 SVG 사용하자.
로딩이슈 없이 깔끔하다.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글