styled-components란 Javascript 파일 내에서 CSS를 사용할 수 있게 해주는 대표적인 CSS-in-JS 라이브러리로 React를 주요 대상으로 한 라이브러리입니다.
Github: https://github.com/styled-components/styled-components
최신 버전 (2022.05.15 기준): v5.3.5
먼저 CSS in JS의 개념을 짚고 넘어가겠습니다.
CSS-in-JS는 스타일 정의를 CSS 파일이 아닌 JavaScript로 작성된 컴포넌트에 바로 삽입하는 스타일 기법입니다. 컴포넌트 내에 정의되어 있기 때문에 해당 컴포넌트가 렌더링 될 때에만 해당 스타일 정보를 읽어들입니다. 이러한 특징이 장점처럼 보일 수 있겠지만 브라우저의 렌더링이 StyleSheet와 Script로 나누어 병렬처리 되던 것이 오직 Script로 이루어짐에 따라 그만큼 속도가 느려집니다.
또한, CSS가 먼저 제공되어 렌더링 시 형태가 잡혀있는 기존 방식에 비해 컴포넌트가 렌더링 되며 형태가 잡히기 때문에 FOUC 현상이 발생되며 이는 사용자 경험을 저하시킵니다.
FOUC란 Flash Of Unstyled Content 의 약자로 스타일 시트가 적용되기 전 마크업 된 그대로의 모습이 잠깐 보이는 현상을 말합니다.
대표적인 CSS-in-JS 라이브러리가 styled-components 입니다.
다음으로 CSS-in-CSS 는 무엇인지 알아보겠습니다. CSS-in-CSS는 스타일 정의를 JS 파일과 분리하여 별도의 CSS 파일에서 관리되는 구조입니다. 그렇기 때문에 어떤 컴포넌트의 상태값이 변하더라도 이에 반응하기 쉽지 않으며 첫 렌더링시 브라우저에 보이지 않는 컴포넌트까지 읽어들이기 때문에 불필요한 컴파일 과정이 추가적으로 발생됩니다.
CSS-in-CSS 방식의 대표적인 라이브러리로는 현재 저희 팀에서 사용하고 있는 scss(sass) 가 있습니다.
화면에 어떤 컴포넌트가 렌더링 되었는지 추적해서 해당하는 컴포넌트에 대한 스타일을 자동으로 삽입합니다. 따라서 코드를 적절히 분배해 놓으면 사용자가 애플리케이션을 사용할 때 최소한의 코드만으로 화면이 띄워지도록 할 수 있습니다.
컴파일 때 자동으로 고유한 클래스 이름을 생성하여 중복, 오타로 인한 버그를 줄여줍니다.
모든 스타일 속성이 특정 컴포넌트와 연결되어 있기 때문에 만약 컴포넌트를 더 이상 사용하지 않아 삭제할 경우 이에 대한 스타일 속성도 함께 삭제됩니다.
className을 일일이 수동으로 관리할 필요 없이 React의 props나 전역 속성을 기반으로 컴포넌트에 스타일 속성을 부여하기 때문에 간단하고 직관적이다.
컴포넌트에 스타일을 상속하는 속성을 찾아 다른 CSS 파일들을 검색하지 않아도 되기 때문에 코드의 크기가 커지더라도 유지보수가 어렵지 않습니다.
개별 컴포넌트마다 기존의 CSS를 이용하여 스타일 속성을 정의하기만 하면 Styled Component가 알아서 처리해 줍니다.
styled-components를 사용해서 React 컴포넌트를 스타일링하는 방법을 살펴보겠습니다.
yarn add styled-components @types/styled-components
먼저 위에서 설치한 styled-components 패키지에서 styled 함수를 임포트합니다. styled는 styled-components의 근간이 되는 가장 중요한 녀석인데요. HTML 엘리먼트나 React 컴포넌트에 원하는 스타일을 적용하기 위해서 사용됩니다.
HTML 엘리먼트를 스타일링 할 때 해당 태그명의 속성에 접근하여 원하는 스타일 정의를 해주면 됩니다.
import styled from "styled-components";
const 컴포넌트명 = styled.태그명`
// <button> HTML 엘리먼트에 대한 스타일 정의
`;
이런 식으로 Styled Components를 이용해서 JavaScript 코드 안에 삽입된 CSS 코드는 글로벌 네임 스페이스를 사용하지 않습니다. 다시 말해, 각 JavaScript 파일마다 고유한 CSS 네임 스페이스를 부여해주기 때문에, 각 React 컴포넌트에 완전히 격리된 스타일을 적용할 수 있게 됩니다.
styled-components는 React 컴포넌트에 넘어온 props에 따라 다른 스타일을 적용하는 기능을 제공합니다. Button 컴포넌트로 정의된 스타일을 공통적으로 사용하되 배경 색상만 다르게 하도록 props를 넘겨보겠습니다.
interface ButtonProps {
bgColor: string;
}
const App = () => {
return (
<Wrapper>
<Button bgColor="Yellow">Yellow</Button>
<Button bgColor="Pink">Pink</Button>
</Wrapper>
);
};
const Button = styled.button<ButtonProps>`
font-size: 1em;
background-color: ${(props) => props.bgColor};
color: black;
border-radius: 3px;
padding: 0.25em 1em;
`;
bgColor라는 props로 변경할 배경색상을 전달해주었습니다. 이렇게 props를 이용한 방식으로 컴포넌트를 재사용할 수 있게 하며 코드 중복을 제거할 수 있습니다. 이따 props를 받는 컴포넌트에 타입을 지정할 때는 styled.태그명<인터페이스명>
와 같이 사용할 수 있습니다.
또한 스타일 확장을 위해 기존에 있는 컴포넌트를 이용해 추가적인 스타일을 갖는 새로운 컴포넌트를 만들어낼 수 있습니다. 앞에서는 html tag를 이용한 컴포넌트를 제작해보았는데요. styled(컴포넌트명) 으로 코드를 작성하여 기존에 컴포넌트를 이용한 새로운 컴포넌트를 만들 수 있습니다.
const App = () => {
return (
<Wrapper>
<Button>Pink</Button>
<YellowButton>Yellow</YellowButton>
</Wrapper>
);
};
const Button = styled.button`
font-size: 1em;
background-color: pink;
color: black;
border-radius: 3px;
padding: 0.25em 1em;
`;
const YellowButton = styled(Button)`
background-color: yellow;
`;
위의 코드를 보시면 Button이라는 컴포넌트로 새로운 YellowButton이라는 컴포넌트를 제작해주었습니다. Button에 정의된 스타일을 그대로 사용하며 배경색상만 노란색으로 나올 수 있도록 재정의 해주었습니다.
기존에 선언한 컴포넌트의 tag를 변경하고 싶을 땐 어떻게 해야 하는지 알아봅시다. as 키워드를 사용해 변경하고 싶은 tag명을 선언해주기만 하면 되는데요 아래 코드와 같이 아주 간단합니다.
const App = () => {
return (
<Wrapper>
<Button>Pink</Button>
<YellowButton as="a">Yellow</YellowButton>
</Wrapper>
);
};
as 키워드를 이용해 기존의 button 속성이 a 태그로 변경된 것을 확인할 수 있습니다.
이번에는 컴포넌트 안에 구성된 자식 컴포넌트를 생성하지 않고 스타일을 정의하는 방법을 보겠습니다.
const App = () => {
return (
<Wrapper>
<Button>
<span>Pink</span>
</Button>
</Wrapper>
);
};
const Button = styled.button`
background-color: pink;
color: black;
border-radius: 3px;
padding: 0.25em 1em;
span {
color: green;
font-size: 1em;
}
`;
Button 컴포넌트 안에 정의된 span 태그의 스타일을 별도의 컴포넌트 생성 없이 선택자를 활용해 정의한 모습을 볼 수 있습니다.
팀원들과의 협업과 일관성 유지를 위해 테마를 지정해 스타일을적용하는 방법에 대해 알아보도록 하겠습니다.
타입 선언
// src/styles/styled.d.ts
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
dark: {
color: string;
background: string;
};
light: {
color: string;
background: string;
};
}
}
먼저, 사용할 스타일들에 대해 타입을 정의해줍니다.
import { DefaultTheme } from "styled-components";
const theme: DefaultTheme = {
dark: {
background: `black`,
color: `white`,
},
light: {
background: "white",
color: `black`,
},
};
export { theme };
공유할 스타일들에 대해 테마로 사용할 폰트, 색상, 레이아웃 등을 선언해줍니다
import React from "react";
import { ThemeProvider } from "styled-components";
import Practice from "./Pratice";
import { theme } from "./styles/theme";
const App = () => {
return (
<ThemeProvider theme={theme}>
<Practice />
</ThemeProvider>
);
};
export default App;
정의한 테마를 사용할 수 있도록 ThemeProvider로 테마를 불러와 컴포넌트를 감싸줍니다.
import React from "react";
import styled from "styled-components";
const Practice = () => {
return (
<Wrapper>
<h1>테마 적용</h1>
</Wrapper>
);
};
const Wrapper = styled.div`
background-color: ${(props) => props.theme.dark.background};
color: ${(props) => props.theme.dark.color};
`;
export default Practice;
정의된 테마에 대한 스타일을 사용하는 모습입니다.
이상으로 React 컴포넌트를 styled-components
를 이용해서 스타일하는 기본적인 방법에 대해서 알아보았습니다. styled-components
을 응용할 수 있는 방법이 무궁무진해 보이고 props를 활용한 조건부 스타일링이 가능하다는 장점이 마음에 들었습니다. 하지만 번들 크기가 커지고 인터랙션이 늦어진다는 단점도 존재하기 때문에 논의를 통해 CSS-in-JS
와 CSS-in-CSS
방식 중 어떤 것을 채택할지 신중히 결정해봐야 될 거 같습니다.