첫 번째와 두 번째 방법은 다른 사이트에 호스팅된 외부 CSS 파일을 가져오므로 네트워크 속도에 영향을 받을 수 있다.
따라서 대부분 웹 애플리케이션은 Node.js 패키지 형태로 구현된 CSS 프레임워크를 내장하는 형태로 배포한다.
<span className="material-icons">home</span>
<span className="material-icons">search</span>
<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 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에 다음과 같이 정의되어 있다고 가정하자.
.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 프레임워크를 내장하는 형태로 배포하는 방식이나 둘 다 깜빡임 현상이 있다.
글자가 잠깐 보였다가 아이콘으로 바뀐다.
해결책으로는 다음의 방법이 있다.
<head>
태그 안에 다음 코드를 추가하여 웹폰트를 미리 로드할 수 있다.
이렇게 하면 웹폰트 로드 시간이 줄어들며, 글자가 보이는 현상도 줄어든다.
<link
rel="preload"
href="https://fonts.gstatic.com/s/materialicons/v114/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2"
as="font"
type="font/woff2"
crossorigin
/>
위의 CSS role을 통해 웹폰트 로드 시 글자가 보이지 않도록 설정할 수 있다.
글자가 한 번에 아이콘으로 전환되기 때문에 덜 튀어 보인다.
.material-icons {
font-display: block;
}
그냥 아이콘은 SVG 사용하자.
로딩이슈 없이 깔끔하다.