이 글은 styled components에 대해 공부하며 정리하는 목적으로 작성 되었습니다. 잘못된 정보는 지적해주시면 바로 정정하겠습니다!
🎀 styled-components
styled components는 javascript에서 css를 사용 할 수 있도록 도와주는 스타일링 프레임워크입니다. React Component에 특정 스타일링을 할 수 있기 때문에 재사용성을 더 높일 수 있고, javascript에 영향을 받는 스타일링을 간편하게 구현 할 수 있습니다.
사용 예시
기존 CSS 스타일링 방식
1
2
3
4
5
6
7
const divStyle = {
backgroundColor: "black",
width: "100px",
height: "100px",
};
return <div style={divStyle}></div>
styled-components를 사용한 방식
1
2
3
4
5
6
7
const StyledDiv = styled.div`
background-color: black;
width: 100px;
height: 100px;
`;
return <StyledDiv></StyledDiv>
기존의 React Component 스타일링은 style 속성에 객체를 전달하거나 className을 설정하고 별도의 css 파일을 import하는 방식으로 사용해왔습니다.
styled-components는 style이 적용된 Component를 직접 생성하기 때문에, 스타일링을 위한 코드 사용량이 줄어드는 효과가 있습니다. 또 key value의 형태가 아닌 css의 문법을 그대로 사용하기 때문에 기존 css의 사용법보다 가독성도 높습니다.
장점
1. component 단위 스타일링
1
<div class="sc-bdnylx iaxDju"></div>
1
2
3
4
5
6
.iaxDju {
background-color: black;
width: 100px;
height: 100px;
display: none;
}
styled-components로 생성된 Components들을 빌드하면 임의의 클래스 명에 스타일이 적용되어 있는 것을 확인 할 수 있습니다. styled-components는 중복되지 않는 특정 class명을 생성해 스타일을 적용하기 때문에, className이 중복되거나 selector의 우선 적용 순위 때문에 css 스타일링이 혼선을 일으키는 사고를 방지 할 수 있습니다.
2. 조건부 스타일링
1
2
3
4
5
6
7
8
9
10
const StyledDiv = styled.div`
background-color: black;
width: 100px;
height: 100px;
${({ login }) => {
return login ? `display: none` : null;
}}
`;
return <StyledDiv login={true}></StyledDiv>;
styled-components는 Component의 props를 전달받아 사용하는 것이 가능합니다. 템플릿 리터럴 내에서 javascript를 사용하는 것과 같은 형식이며, 내부에서 선언된 함수는 props를 파라미터로 실행됩니다.
3. 확장 스타일링
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Container = styled.div`
max-width: 600px;
width: 100%;
height: 100px;
`;
const BlackContainer = styled(Container)`
background-color: black;
`;
const RedContainer = styled(Container)`
background-color: red;
`;
return (
<>
<BlackContainer />
<RedContainer />
</>
);
styled-components는 새로운 Component를 선언하는 것 뿐만 아니라, 기존의 Component에 스타일을 추가하는 것도 가능합니다. 확장 스타일링을 사용하면 중복된 코드 양을 줄이고, 분산된 스타일을 일관적으로 관리 할 수 있어 유지보수 비용을 줄일 수 있습니다.
1
2
3
4
5
6
7
8
9
10
const MyLink = styled(Link)`
color: black;
text-decoration: none;
`;
return (
<Router>
<MyLink to="/main"> 커스텀 링크 </MyLink>
</Router>
);
기존 Component에 스타일을 추가 할 수 있는 기능 덕분에, 서드 파티 Component와도 호환이 가능합니다. 예를 들면 자주 사용하는 React-router-dom의 Link Component의 경우에도 위와 같은 방법으로 스타일을 적용해 사용 할 수 있습니다.
4. 중첩 스코프
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const StyledDiv = styled.div`
background-color: black;
width: 100px;
height: 100px;
p {
color: white;
}
`;
return (
<>
<StyledDiv>
<p>Title</p>
</StyledDiv>
<p>content</p>
</>
);
SASS의 중첩 스코프 규칙을 사용할 수 있습니다. 덕분에 내부의 모든 component를 styled-components로 생성하지 않아도, 하위 컴포넌트에게만 적용하고 싶은 스타일을 스코프 형태로 구현 할 수 있습니다. (주의: 모든 SASS 문법이 사용 가능하진 않습니다.)
🔧 프로젝트 세팅하기
1. Install
1
2
3
4
5
6
7
// CRA로 프로젝트 생성
$ create-react-app styled-components-project
// styled-components 설치하기
$ npm install --save styled-components
// yarn을 사용한다면
$ yarn add styled-components
CRA로 앱을 생성한 후 styled-components를 설치합니다.
1
2
3
4
5
import styled from "styled-components";
...
const CustomDiv = styled.div`
...
`;
앞으로 스타일링된 Component를 생성 할 때는 styled라는 이름으로 styled-components 모듈을 import해, 위와 같은 문법으로 정의합니다.
2. theme
스타일링을 시작하기 앞서, 자주 사용하게 될 색상 코드, 사이즈, 폰트, 미디어 쿼리 등의 정보를 변수로 생성해 사용하면 일관적인 스타일 관리가 가능합니다. styled-component에선 전반적으로 프로젝트 전체에서 사용할 목적의 변수 모음 객체를 theme이라고 부릅니다.
SASS 사용 경험이 있으시다면, variable을 별도의 파일로 분리해 main 파일에서 관리하던 것과 유사한 목적으로 사용되는 객체입니다!
/src/styles/theme.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
const margins = {
sm: ".5rem",
base: "1rem",
lg: "2rem",
xl: "3rem",
};
const paddings = {
sm: ".5rem",
base: "1rem",
lg: "2rem",
xl: "3rem",
};
const fonts = {
family: {
base: `'Noto Sans KR', sans-serif`,
title: `'Merriweather', serif`,
},
size: {
sm: "1.4rem",
base: "1.6rem",
lg: "2rem",
xl: "2.5rem",
title: "6rem",
},
weight: {
light: 100,
normal: 400,
bold: 700,
},
};
const colors = {
red: "#ff4d4d",
yellow: "#ffff4d",
blue: "#0099ff",
};
const size = {
mobile: "425px",
tablet: "768px",
desktop: "1440px",
};
// 미디어 쿼리의 중복 코드를 줄이기위해 정의된 변수입니다
const device = {
mobile: `@media only screen and (max-width: ${size.mobile})`,
tablet: `@media only screen and (max-width: ${size.tablet})`,
desktopL: `@media only screen and (max-width: ${size.desktop})`,
};
//테마에 따라 다른 값을 갖는 색상 값입니다
const lightThemeColors = {
...colors,
primary: "#333",
secondary: "#fff",
tertiary: "#808080",
};
const darkThemeColors = {
...colors,
primary: "#fff",
secondary: "#333",
tertiary: "#d4d0c4",
};
// 테마와 관련없이 공통으로 사용되는 변수들입니다
const defalutTheme = {
margins,
paddings,
fonts,
device,
};
// 각 테마는 공통 변수와 함께, 각기 다른 색상 값들을 갖습니다.
export const darkTheme = {
...defalutTheme,
colors: darkThemeColors,
};
export const lightTheme = {
...defalutTheme,
colors: lightThemeColors,
};
/src/components/App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState } from "react";
import { ThemeProvider } from "styled-components";
import { darkTheme, lightTheme } from "../styles/Theme";
import Header from "./Header";
import Container from "./Container";
const App = () => {
const [theme, setTheme] = useState(lightTheme);
const [currentThemeText, setCurrentThemeText] = useState("Light Theme");
// 각 theme은 state로 관리되며 버튼 클릭 이벤트 시 변경됩니다.
const switchTheme = () => {
const nextTheme = theme === lightTheme ? darkTheme : lightTheme;
setTheme(nextTheme);
const nextThemeText = theme === lightTheme ? "Dark Theme" : "Light Theme";
setCurrentThemeText(nextThemeText);
};
return (
//ThemeProvider는 반드시 theme을 사용 할 하위 Component를 감싸고 있어야 합니다.
<ThemeProvider theme={theme}>
<Header switchTheme={switchTheme} />
<Container currentThemeText={currentThemeText} />
</ThemeProvider>
);
};
export default App;
이렇게 생성한 theme 객체는 각 Component마다 import해 사용 할 수도 있지만, 더 간단하게 사용할 수 있는 방법이 있습니다! 바로 styled-components가 지원하는 ThemeProvider를 사용하는 것입니다.
- ThemeProvider: 반드시 theme 속성을 갖는 컴포넌트. theme 속성의 값으로 설정된 객체를 모든 하위 컴포넌트들의 props로 전달합니다.
이렇게 ThemeProvider를 사용하면 굳이 props나 useContext를 사용하지 않아도 모든 컴포넌트에서 theme 객체에 간편하게 접근 할 수 있습니다. 또 사용자 theme 변경 이벤트가 발생 했을시, Provider의 theme 속성 값만 변경하는 것으로 간단하게 테마 변경을 구현 할 수 있습니다.
3. global-style
reset, normalize, css-file
styled-componenst에서는 Component 단위가 아닌 HTML Elements 단위로 적용되는 style을 global-style에서 관리합니다. 예를 들면 reset, normalize 등의 기본적인 스타일이 이 경우에 포함됩니다.
1
2
3
4
5
6
7
8
9
10
// reset과 normalize 설치
$ npm install --save styled-reset
$ npm install --save styled-normalize
// yarn을 사용한다면
$ yarn add styled-reset
$ yarn add styled-normalize
// 별도의 sass의 파일을 사용한다면 node-sass 설치
$ npm install --save node-sass
$ yarn add node-sass
-
styled-reset: Eric Meyer’s Reset CSS을 npm 모듈로 설치해 사용 할 수 있습니다.
-
styled-normalize: npm 모듈로 지원되는 normalize입니다.
스타일링에 기본적으로 세팅하는 reset은 npm 모듈로 간편하게 설치해 불러 올 수 있습니다. 반드시 위의 모듈을 사용할 필요는 없으며, 개인적으로 설정해둔 css, scss 파일이 있다면 그 또한 import해 사용 할수 있습니다.
/src/styles/customReset.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
html,
body {
width: 100%;
height: 100%;
}
html {
font-size: 62.5%; //1rem = 10px;
}
body {
font-size: 1.6rem;
}
createGlobalStyle
/src/styles/GlobalStyle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createGlobalStyle, css } from "styled-components";
import reset from "styled-reset";
import customReset from "./customReset.scss";
const GlobalStyle = createGlobalStyle`
${reset};
${customReset};
html {
font-size: 62.5%; //1rem = 10px;
}
${({ theme }) => {
return css`
body {
font-family: ${theme.fonts.family.base};
font-weight: ${theme.fonts.weight.normal};
font-size: ${theme.fonts.size.base};
}
`;
}}
`;
export default GlobalStyle;
styled-components가 지원하는 createGlobalStyle를 이용하면 페이지 전체에 적용될 스타일을 선언 할 수 있습니다. 템플릿 리터럴에 일반적인 css와 같은 문법으로 사용하면 되기 때문에 기존의 코드를 쉽게 재사용할 수 있습니다.
-
6,7번 줄: css 파일이나 별도로 설치한 스타일 모듈도 import해 사용할 수 있습니다. 템플릿 리터럴 안에서 import된 모듈 변수명를 불러오면 됩니다.
-
9번 줄: 그 외에 추가적으로 선언하고 싶은 스타일이 있다면 css 문법으로 편리하게 선언 가능합니다.
-
13번 줄: props와 관련된 스타일은 함수로 선언합니다. 선언된 함수는 Component가 전달받은 props들을 파라미터로 실행됩니다. 위의 코드에선 props중 theme 속성을 구조 분해 할당시켜 사용했습니다.
-
14번 줄: return 값은 string으로 인식되기 때문에 반드시 css 리터럴 선언이 필요하진 않습니다. 하지만 css 리터럴 선언을 사용하면 formatter가 다시 스타일 코드를 인식하게 되어 가독성과 줄정렬에 도움이 됩니다.
/src/components/App.js
1
2
3
4
5
6
7
8
...
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Header switchTheme={switchTheme} />
<Container currentThemeText={currentThemeText} />
</ThemeProvider>
);
정의된 GlobalStyle 컴포넌트는 theme을 props으로 받을 수 있도록 ThemeProvider의 하위 컴포넌트로 배치합니다.
4. Styled-Component 정의
이제 설정된 global-style과 theme을 이용해 컴포넌트를 스타일링 해보겠습니다.
Header
/src/styles/Header-style.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import styled, { css } from "styled-components";
// theme을 변경하는 btn의 스타일링
const ThemeSwitchBtn = styled.button`
${({ theme }) => {
//props로 전달받은 theme 속성을 사용한다.
// App의 theme state가 변경되면 컴포넌트가 재 렌더링되며 다른 색상 값들을 갖게 된다.
return css`
background-color: ${theme.colors.primary};
color: ${theme.colors.secondary};
font-size: ${theme.fonts.size.base};
border-radius: 2px;
`;
}}
`;
const CustomHeader = styled.div`
${({ theme }) => {
return css`
padding: 1rem;
display: flex;
justify-content: center;
background-color: ${theme.colors.secondary};
`;
}}
`;
const styledComponents = { ThemeSwitchBtn, CustomHeader };
export default styledComponents;
styled-components 선언은 Component 함수 스코프 외부에 위치 해야하는 규칙을 갖고 있습니다. 함수 내에 선언한다고 오류가 발생하지는 않지만, 콘솔에 Component와 styled-component를 분리하길 권장하는 경고 메시지가 표시됩니다. 또 스타일 코드는 Component의 props와 state에 영향을 주지 않기 때문에 별도의 파일로 분리해 관리하겠습니다.
/src/components/Header.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import styledComponents from "../styles/Header-style";
const { CustomHeader, ThemeSwitchBtn } = styledComponents;
const Header = ({ switchTheme }) => {
return (
<CustomHeader>
<ThemeSwitchBtn
onClick={() => {
switchTheme();
}}
>
Change Theme
</ThemeSwitchBtn>
</CustomHeader>
);
};
export default Header;
스타일 코드를 별도로 분리한 덕에 코드 가독성이 좋아졌습니다.
- CustomHeader: styled-components 선언에서 theme 속성을 이용했기 때문에 theme 변경 이벤트시 재 렌더링 되며 다른 스타일 값을 갖게 됩니다.
- ThemeSwitchBtn: 클릭시 switchTheme를 실행시켜 App의 theme state를 변경합니다. 마찬가지로 theme에 영향을 받는 스타일 속성을 갖고있어, theme 변경시 다른 스타일로 표시됩니다.
Container
/src/styles/Container-style.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import styled, { css } from "styled-components";
const CustomContainer = styled.div`
${({ theme }) => {
const { colors, device, fonts, paddings } = theme;
return css`
width: 100%;
height: 100%;
background-color: ${colors.tertiary};
${device.tablet} {
background-color: ${colors.red};
}
h1 {
font-size: ${fonts.size.xl};
padding: ${paddings.xl};
text-align: center;
}
`;
}}
`;
const styledComponents = { CustomContainer };
export default styledComponents;
- 10번 줄: theme에 선언했던 미디어 쿼리를 사용해 코드를 축약시켰습니다. 브라우저의 너비가 tablet에 속할 경우 배경 색이 변경됩니다.
- 14번 줄: styled-components의 중첩 선언입니다. CustomContainer 하위에 위치하는 h1 요소만 이 스타일 코드에 영향을 받습니다.
/src/components/Container.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import styledComponents from "../styles/Container-style";
const { CustomContainer } = styledComponents;
const Container = ({ currentThemeText }) => {
return (
<CustomContainer>
<h1>{currentThemeText}</h1>
</CustomContainer>
);
};
export default Container;
[Posting Reference]
styled-components Documentation
8 reasons to use styled-components
Implementing Dark Mode In React Apps Using styled-components
How to use media queries with styled components