styled-component의 동작 원리와 주의사항을 알아봅니다.
본문에선 언급하지 않았으나, 실제로 styled-components는 useMemo, useCallback과 같은 도구를 이용하여
자체적인 최적화를 많이 수행하고 있습니다.
따라서 불필요한 css 재평가 부하에 대해선 걱정할 필요가 없습니다.
1. styled-components가 CSS를 생성 및 주입하는 방법
- styled-components는 css를 lazy 하게 생성 및 삽입함
- 컴포넌트 렌더링 전까지 탬플릿 리터럴, 혹은 객체로 전달된 css 규칙을 평가하지도 않고, cssom에 반영하지 않음
- 렌더링하는 css에 대해서만 css 평가 비용을 지불 (비교 - 사전 컴파일 tailwind)
- js는 defer를 사용하여 다운로드, 처리 전에 화면을 렌더링하도록 할 수 있지만, 브라우저는 css를 전부 다운로드 완료하고 처리하기 전까지 화면을 렌더링하지 않음
- styled-components는 cssom을 이용해 css 규칙을 추가함
- document.stylesheets[0], styleSheet.insertRule(rulesets)
예시 코드 (실제와 다름)
function h1(styles) {
return function NewComponent(props) {
// styles 객체, 혹은 태그드 탬플릿 리터럴을 활용해 해시된 클래스 명 생성
const uniqueClassName = comeUpWithUniqueName(styles);
// 해당 객체를 sass, less 등의 규칙을 처래히주는 Stylus css processor를 통과시켜 실제 css 룰셋을 생성
const processedStyles = runStylesThroughStylis(styles);
// class명과 css룰셋을 cssom에 삽입함
createAndInjectCSSClass(uniqueClassName, processedStyles);
return <h1 className={uniqueClassName} {...props} />
}
}
2. styled-components가 tag를 결정하는 방법
아래 문법은 실제로 사용할 수 있음
const Tag = 'button';
return <Tag>Hello</Tag>
아래 코드는 바벨 등의 트랜스파일러를 통해 다음과 같이 변경됨.
즉, 첫번째 인자가 컴포넌트냐, 문자열이냐는 중요하지 않음
(소문자면 htmlEement, 첫번째 글자가 대문자면 컴포넌트로 판단하기에 네이밍 규칭이 중요한 것)
const Tag = 'button';
React.createElement(Tag, {}, "Hello");
따라서 1번에서 설명한 코드는 실제로 아래와 유사하다고 생각할 수 있음
const styled = (Tag) => (styles) => {
return function NewComponent(props) {
const uniqueClassName = comeUpWithUniqueName(styles);
createAndInjectCSSClass(uniqueClassName, styles);
return <Tag className={uniqueClassName} {...props} />
}
}
3. 컴포넌트 합성
1번에서 설명하였듯, 실제 styled-components는 className을 이용해 컴포넌트에 스타일을 적용함
따라서, 합성 대상 컴포넌트에 className 프롭이 없으면 동작하지 않음
아래 코드는 합성할 수 있음
function Message({ children, ...delegated }) {
console.log(delegated);
// { className: 'OkjqvF' }
return (
<p {...delegated}>
You've received a message: {children}
</p>
);
}
아래 코드는 합성할 수 없음
function Message({ children }) {
/*
Because we're ignoring the `className` prop,
the styles will never be set.
*/
return (
<p>
You've received a message: {children}
</p>
);
}
지금까지 합성을 잘 활용해왔다면, 합성한 컴포넌트가 위의 컨벤션을 잘 따라온 것임.
또한 CSS 규칙은 충돌 시에, 뒤에 나오는 규칙을 적용하도록 되어있음.
예를 들어 아래의 두 paragraph내 텍스트는 전부 파란색으로 렌더링 됨.
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
<!-- blue -->
<p class="blue red">Hello</p>
<!-- blud -->
<p class="red blue">Hello</p>
styled-components는 리액트 컴포넌트 함수(명)와 렌더링 순서를 고려하여 css 규칙을 추가하여
해당 문제를 알아서 해결해줌
const styled = (Tag) => (styles) => {
return function NewComponent(props) {
const uniqueClassName = comeUpWithUniqueName(styles);
const processedStyles = runStylesThroughStylis(styles);
createAndInjectCSSClass(uniqueClassName, processedStyles);
// 클래스 명을 결합하여 반영
const combinedClasses =
[uniqueClassName, props.className].join(' ');
return <Tag {...props} className={combinedClasses} />
}
}
4. Style 보간(Interpolated styles)
아래 styled-components를 활용하여
const ContentImage = styled.img`
display: block;
margin-bottom: 8px;
width: 100%;
max-width: ${p => p.maxWidth};
`;
다음 JSX를 렌더링 하였다고 가정하면,
<>
<ContentImage
alt="A running shoe with pink laces and a rainbow decal"
src="/images/shoe.png"
maxWidth="200px"
/>
<ContentImage
alt="A close-up shot of the same running shoe"
src="/images/shoe-closeup.png"
/>
</>
아래와 같이 css 규칙이 두 개 생긴다.
<style>
.JDSLg {
display: block;
margin-bottom: 8px;
width: 100%;
max-width: 200px;
}
.eXyedY {
display: block;
margin-bottom: 8px;
width: 100%;
}
</style>
<img
alt="A running shoe with pink laces and a rainbow decal"
src="/images/shoe.png"
class="sc-bdnxRM JDSLg"
/>
<img
alt="A close-up shot of the same running shoe"
src="/images/shoe-closeup.png"
class="sc-bdnxRM eXyedY"
/>
이는, css가 런타임에 생성 및 평가된다는 것과, 사전에 css 규칙들을 미리 생성해 둘 수 없다는 것을 의미한다.
따라서, 해당 사용사례는 공식 문서에서 추천하지 않는 방법이다.
상항에 따라 너무 많은 스타일 규칙이 생성될 수 있다.
한가지 방법은 style 속성을 이용하는 것이다.(참고 : use-the-style-prop-for-dynamic-styles)
<ContentImage
alt="A running shoe with pink laces and a rainbow decal"
src="/images/shoe.png"
style={{maxWidth : '200px'}}
/>
다른 방법은 css variable를 사용하는 것이다. (참고 : advanced-css-variables-with-style)
이전의 예시는 아래와 같이 변경될 것이다.
const ContentImage = styled.img`
display: block;
margin-bottom: 8px;
width: 100%;
max-width: var(--max-width);
`;
const Component = ()=>(
<>
<ContentImage
alt="A running shoe with pink laces and a rainbow decal"
src="/images/shoe.png"
style={{
'--max-width': '200px',
}}
/>
<ContentImage
alt="A close-up shot of the same running shoe"
src="/images/shoe-closeup.png"
/>
</>
)
css Variables는 디폴트 값 사용도 허용한다.
const Wrapper = styled.div`
opacity: var(--opacity, 0.75);
background-color: var(--color, var(--color-gray-900));
`;
링크에서 언급하는 방법을 통해 타입스크립트 지원을 추가할 수 있다.
how-to-define-css-variables-in-style-attribute-in-react-and-typescript
정리
지금까지 내용을 코드로 옮긴다면 다음과 같이 될 것이다.
styled-components 자체적인 최적화 로직과, 태그드 탬플릿 리터럴을 처리하는 부분들을 매우 간소화 한다면 말이다.
const styled = (Tag) => (rawStyles, ...interpolations) => {
return function NewComponent(props) {
/*
템플릿 문자열을 이용해 스타일을 계산,
React props와 보간 함수를 고려해 스타일 규칙 생성
*/
const styles = reconcileStyles(
rawStyles,
interpolations,
props
)
/* 나머지는 이전과 동일: */
const uniqueClassName = comeUpWithUniqueName(styles);
const processedStyles = runStylesThroughStylis(styles);
createAndInjectCSSClass(uniqueClassName, processedStyles);
const combinedClasses =
[uniqueClassName, props.className].join(' ');
return <Tag {...props} className={combinedClasses} />
}
}
지금까지의 다룬 내용을 요약하자면 다음과 같다.
- styled-components는 컴포넌트 명과 css 규칙을 이용하여 className명(해시)을 만들고, class를 이용해 컴포넌트에 css를 적용
- css는 런타임에 평가되므로, 렌더링 대상 컴포넌트들에 대해서만 css 평가 및 cssom 조작 비용을 지불.
- 컴포넌트 합성을 위해선 className 속성을 지원해야 함. 규칙 충돌 시 오버라이딩하는 규칙 적용은 styled-components가 핸들링.
- 동적 스타일이 필요하면, style interpolation보다 css variables를 사용하는 것이 좋음
참고
https://www.joshwcomeau.com/react/demystifying-styled-components/
https://emotion.sh/docs/best-practices
'FrontEnd' 카테고리의 다른 글
styled-components와 flexbox를 이용한 2D 레이아웃 디자인 (0) | 2023.02.19 |
---|---|
styled-components best practices(모범 사례) (0) | 2023.02.17 |
빅테크 프론트엔드 기술 인터뷰 : JS 편 (0) | 2023.02.17 |
React의 cloneElement API, 기존 엘리먼트를 기반으로 새로운 엘리먼트 생성하는 방법 알아보기 (0) | 2023.02.16 |
리액트 디자인 패턴(React design pattern) : Compound Component Pattern(컴파운드 컴포넌트패턴)과 Uncontrolled Component Pattern(유상태 컴포넌트 패턴) (0) | 2023.02.15 |