1. Styled Components 시작하기
Styled Componets란?
Styled Components
는 React에서 사용되는 CSS-in-JS 라이브러리로, 컴포넌트 스타일링을 더욱 쉽고 간편하게 만들어주는 도구입니다.
Styled Components
를 사용하면 컴포넌트와 스타일을 하나의 파일에서 정의할 수 있습니다. 이는 컴포넌트와 스타일을 분리하지 않아도 되므로 코드 유지보수를 더욱 용이하게 만들어줍니다. 또한, 동적으로 스타일을 변경하는 것이 가능하며, 컴포넌트에 스타일을 적용하는 것이 더욱 직관적이고 간단해집니다.
- Styled Components 예시 코드
import styled from 'styled-components';
const Button = styled.a`
/* This renders the buttons above... Edit me! */
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
border: 2px solid white;
/* The GitHub button is a primary button
* edit this to target it specifically! */
${props => props.$primary && css`
background: white;
color: black;
`}
`
2. 기존 방식의 문제점
CSS 클래스 이름이 겹치는 문제
- 클래스 이름은 전역적인 특성을 가지기 때문에, 부모 컴포넌트의 CSS 파일에서도 같은 클래스 이름이 사용된 부분이 있으면 import 해오지 않은 CSS 파일에서의 스타일이 적용될 수 있습니다. 이렇게 의도하지 않은 방식으로 스타일이 적용되는 걸 막기 위해 클래스 이름은 겹치지 않도록 조심해야 합니다.
- 하지만 대규모 프로젝트에서는 겹치지 않는 클래스 이름을 짓기가 어렵다는 문제가 있습니다.
Styled Components
는 클래스 이름을 아예 쓰지 않기 때문에 클래스 이름이 겹칠 일이 없도록 만들어 줍니다.
재사용하는 CSS 코드를 관리하기 어렵다는 문제
- JavaScript와 달리 CSS 코드는 VSCode 같은 코드 에디터에서 추적하기 어렵기 때문에 직접 텍스트로 하나하나 검색을 해야 하기 때문에, 스타일이 재사용 되는 곳이 많아질수록 코드를 유지 보수 하기 어려워집니다.
Styled Components
는 스타일 재사용이 필요한 상황에서 클래스가 아니라 JavaScript 변수를 만들기 때문에, 언제 어디서 쓰고 있는지 에디터를 통해 확인하기 쉬워지고, 이름을 바꾸거나 삭제를 하는 것도 쉽게 할 수 있게 해줍니다.
복잡한 선택자를 쓸 때 생기는 문제
- 여러 선택자를 사용하는 복잡한 선택자는 CSS의 우선순위를 결정하는 규칙을 복잡해지게 만들기 때문에 스타일의 변화가 예상치 못하게 일어나는 원인이 됩니다.
Styled Components
는 복잡한 선택자를 사용하지 않으므로 스타일 규칙의 우선순위를 결정하는 규칙을 단순하게 만들어주고 코드 유지보수를 더욱 쉽게 만들어줍니다.
3. Styled Components의 특징
Automatic critical CSS
- 스타일드 컴포넌트는 페이지에서 어떤 컴포넌트가 렌더링되는지 추적하고 해당 스타일만 삽입하며 다른 스타일은 완전히 자동으로 삽입합니다.
- 코드 분할과 함께 사용하면 사용자가 필요한 최소한의 코드를 로드할 수 있습니다.
No class name bugs
- 스타일드 컴포넌트는 스타일에 고유한 클래스 이름을 생성합니다. 따라서 duplication, overlap, misspellings에 대해 걱정할 필요가 없습니다.
Easier deletion of CSS
- 코드베이스 어딘가에서 클래스 이름이 사용되었는지 알기 어려울 수 있지만, 스타일드 컴포넌트는 모든 스타일이 특정 컴포넌트에 연결되어 있기 때문에 명확하게 알 수 있습니다.
- 도구가 감지했을 때 컴포넌트가 사용되지 않거나, 컴포넌트가 삭제되면 모든 스타일도 함께 삭제됩니다.
Simple dynamic styling
- prop이나 글로벌 테마를 기반으로 컴포넌트의 스타일을 조정하면 수십 개의 클래스를 수동으로 관리할 필요 없이 간단하고 직관적으로 스타일을 적용할 수 있습니다.
Painless maintenance
- 컴포넌트에 영향을 미치는 스타일링을 찾기 위해 여러 파일을 찾아다닐 필요가 없으므로 코드베이스의 규모에 관계없이 유지 관리가 매우 간편합니다.
Automatic vendor prefixing
- 현재 표준에 맞게 CSS를 작성하면 스타일드 컴포넌트가 자동으로
vendor prefix
를 처리해줍니다.
Vendor prefix
- 브라우저가 새로운 CSS 속성을 구현하기 전에 해당 속성 이름 앞에 추가하는 접두사(prefix)입니다. 이는 해당 브라우저에서만 해당 CSS 속성을 지원하기 때문에, 다른 브라우저에서는 제대로 작동하지 않을 수 있습니다.
- 따라서 개발자는
-webkit-*
, -moz-*
등과 같은 vendor prefix를 추가하여 모든 브라우저에서 해당 CSS 속성이 일관되게 작동하도록 보장해야 합니다.
4. Styled Components installation
npm install styled-components
yarn add styled-components
resolutions
package.json field를 지원하는 yarn
과 같은 패키지 관리자를 사용하는 경우 주요 버전 벌위에 해당하는 항복도 추가할 것을 권장합니다. 이렇게 하면 프로젝트에 여러 버전의 스타일 컴포넌트가 설치되어 발생하는 종류의 문제를 방지할 수 있습니다.
{
"resolutions": {
"styled-components": "^5"
}
}
- Babel 플러그인도 함께 사용하는 것을 적극 권장합니다. 더 읽기 쉬운 클래스 이름, 서버 측 렌더링 호환성, 더 작은 번들 등 많은 이점을 제공합니다.
npm install
yarn add
5. Nesting 문법
& 선택자
- Nesting에서
& 선택자
는 부모 선택자를 참조합니다.
const Button = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
&:hover,
&:active {
background-color: #463770;
}
`;
export default Button;
.Button {
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
}
.Button:hover,
.Button:active {
background-color: #463770;
}
컴포넌트 선택자
- 하위 컴포넌트를 선택자로 쓰고 싶을 때는 ${Component}같이 컴포넌트 자체를 템플릿 리터럴 안에 넣어주면 됩니다.
const Icon = styled.img`
width: 16px;
height: 16px;
`;
const StyledButton = styled.button`
& ${Icon} {
margin-right: 4px;
}
`;
function Button({ children, ...buttonProps }) {
return (
<StyledButton {...buttonProps}>
<Icon src={nailImg} alt="nail icon" />
{children}
</StyledButton>
);
}
export default Button;
.Icon {
width: 16px;
height: 16px;
}
.StyledButton .Icon {
margin-right: 4px;
}
&
와 자손 결합자를 사용하는 경우에는 &
를 생략할 수 있습니다.
const StyledButton = styled.button`
/*
& ${Icon} {
mrgin-right: 4px;
}
*/
${Icon} {
margin-right: 4px;
}
`;
- Nesting은 여러 겹으로 할 수도 있습니다.
const StyledButton = styled.button`
&:hover,
&:active {
${Icon} {
opacity: 0.2;
}
}
`;
.StyledButton:hover .Icon,
.StyledButton:active .Icon {
opacity: 0.5;
}
6. 다이나믹 스타일링
${ ... } 안에 값(변수) 사용하기
- 템플릿 리터럴의 기능으로 값을 넣을 수 있습니다.
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
...
font-size: ${SIZES['medium']}px;
`;
.Button {
font-size: 20px;
}
${ ... } 안에 함수 사용하기
- Styled Components는 ${ ... } 안에 함수를 사용할 수 있게 해줍니다.
- 함수의 파라미터로 props를 받고 스타일 코드를 리턴하도록 작성합니다.
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
`;
- 널 병합 연산자(Nullish coalescing operator), 구조 분해(Destructuring)를 활용할 수 있습니다.
논리 연산자 사용하기
const Button = styled.button`
${({round}) => round && `
border-radius: 9999px;
`}
`;
7. 상속(inheritance)
styled() 함수
- Styled Components로 만들어진 컴포넌트를 상속하려면
styled()
함수를 사용하면 됩니다.
import styled from 'styled-components';
const Button = styled.button`
// ...
`;
const SubmitButton = styled(Button)`
// ...
`;
- Button 컴포넌트의 스타일을 상속해서 새로운 버튼 SubmitButton을 만드는 코드입니다.
Styled Components를 사용하지 않고 만들어진 컴포넌트 상속
- 컴포넌트의 Prop에 className 값을 내려줘야
styled()
함수로 상속할 수 있습니다.
function TermsOfService({ className }) {
return (
<div className={className}>
...
</div>
);
}
8. css 함수
- 반복되는 코드는 한 곳에서 지정하고 여러 군데서 활용하려할 때 styled-components의
css
함수를 사용할 수 있습니다.
import styled, { css } from 'styled-components';
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const fontSize = css`
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
`;
const Button = styled.button`
${fontSize}
`;
const Input = styled.input`
${fontSize}
`;
- CSS 스타일 변수가 함수를 삽입하지 않는 템플릿 문자열이더라도 항상
css
함수를 사용해서 만드는 것을 권장합니다.
9. 글로벌 스타일
- 글로벌 스타일을 css 함수를 사용해서 변수로 만들고 사용할 수도 있지만 모든 컴포넌트에 일일이 값을 넣어주어야 하는 문제가 있습니다.
- 이럴 땐
global style component
를 최상위 컴포넌트에서 렌더링 하면 글로벌 스타일이 항상 적용된 상태가 되도록 할 수 있습니다.
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
}
body {
font-family: 'Noto Sans KR', sans-serif;
}
`;
function App() {
return (
<>
<GlobalStyle />
<div>글로벌 스타일</div>
</>
);
}
export default App;
createGlobalStyle
은 html <style>
태그 안에 작성한 CSS 코드가 오버라이딩되도록 내부적으로 처리해줍니다.
10. 애니메이션
@keyframes
- CSS 애니메이션의 keyframe을 정의할 수 있는 규칙입니다.
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
.spinner {
animation: rotate 1.5s linear infinite;
}
keyframes 함수
keyframes
함수는 Styled Components에서 @keyframes
css 규칙을 사용하고 싶을 때 사용합니다.
import styled, { keyframes } from 'styled-components';
const rotate = keyframes`
100% {
transform: rotate(360deg);
}
`;
const Spinner = styled.div`
animation: ${rotate} 1.5s linear infinite;
`;
11. 테마
테마(Theme)
- UI에서 사용되는 색상, 글꼴, 배경 이미지 등과 같은 요소를 변경하여 브라우저의 모양과 느낌을 바꿀 수 있는 기능입니다.
Light Mode
테마와 Dark Mode
테마를 선택하는 기능이 자주 사용됩니다.
ThemeProvider로 테마 설정 사용하기
- 테마로 설정된 값은 전역에서 사용되기 때문에, Styled Components에서도 React의 Context를 기반으로 테마를 사용합니다.
ThemeProvider
는 styled-components에서 테마 객체를 내려주는 context provider입니다.
import { ThemeProvider, createGlobalStyle } from 'styled-components';
import Input from './Input';
const THEMES = {
light: {
backgroundColor: '#ffffff',
color: '#000000',
},
dark: {
backgroundColor: '#121212',
color: '#ffffff',
},
};
const GlobalStyle = createGlobalStyle`
body {
background-color: ${({ theme }) => theme.backgroundColor};
color: ${({ theme }) => theme.color};
}
`;
function App() {
const theme = THEMES['light'];
return (
<ThemeProvider theme={theme}>
<div>
<GlobalStyle />
<Input />
</div>
</ThemeProvider>
);
}
export default App;
const StyledInput = styled.input`
background-color: ${({ theme }) => theme.backgroundColor};
color: ${({ theme }) => theme.color};
`;
- 여러 테마를 선택하게 하고 싶다면
useState
를 활용해서 테마를 선택하게 만들 수 있습니다.
import { useState } from 'react';
function App() {
const [theme, setTheme] = useState(THEMES['light']);
const handleSelectChange = (e) => {
const nextThemeName = e.target.value;
setTheme(THEMES[nextThemeName]);
};
return (
<ThemeProvider theme={theme}>
<div>
<select onChange={handleSelectChange}>
<option value="light">라이트 모드</option>
<option value="dark">다크 모드</option>
</select>
<GlobalStyle />
<Input />
</div>
</ThemeProvider>
);
}
export default App;
- 테마 설정 페이지처럼 테마 값을 일반적인 컴포넌트에서 참조할 필요가 생긴다면
ThemeContext
를 불러오면 됩니다.
ThemeContext
는 React Context
이기 때문에 useContext
hook으로 접근합니다.
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
function SettingPage() {
const theme = useContext(ThemeContext);
}
12. 상황별 유용한 팁
버튼 모양 링크가 필요할 때
- 스타일은 버튼 스타일이지만
<a>
태그로 사용하고 싶을 때 같은 경우, as
라는 키워드로 태그 이름을 내려주면 해당 태그로 사용할 수 있습니다.
const Button = styled.button`
// ...
`;
<Button href="https://example.com" as="a">
LinkButton
</Button>
원치 않는 Props가 전달될 때
- 아래처럼 Prop을 Spread 문법을 사용해서
<a>
태그로 전달하는 Link 컴포넌트가 있고 StyledLink
라는 걸 만들어서 underline
이라는 불린 Prop으로 스타일링 해봤습니다.
import styled from 'styled-components';
function Link({ className, children, ...props }) {
return (
<a {...props} className={className}>
{children}
</a>
);
};
const StyledLink = styled(Link)`
text-decoration: ${({ underline }) => underline ? `underline` : `none`};
`;
function App() {
return (
<StyledLink underline={false} href="https://codeit.kr">
Codeit으로 가기
</StyledLink>
);
}
export default App;
react-dom.development.js:86 Warning: Received `false` for a non-boolean attribute `underline`.
If you want to write it to the DOM, pass a string instead: underline="false" or underline={value.toString()}.
If you used to conditionally omit it with underline={condition && value}, pass underline={condition ? value : undefined} instead.
at a
at Link (http:
at O (http:
at App
- 위 오류는 React에서 알려주는 오류입니다. HTML 태그에 underline 이라는 속성을 지정했는데, 그 속성의 값이 문자열이 아니라서 생긴 오류입니다.
<a>
태그에는 underline 이라는 속성이 없고, <Link>
자식 컴포넌트에서 spread를 하는 과정에서 의도하지 않은 underline prop까지 내려간 것이 원인입니다.
- 일반적으로 모든 prop은 자식 컴포넌트로 전달되지만,
Transient Prop
은 해당 컴포넌트에서만 사용될 수 있는 prop입니다.
- Prop 앞에
$
기호를 붙이면 Transient prop
으로 사용할 수 있습니다.
import styled from 'styled-components';
const StyledLink = styled(Link)`
text-decoration: ${({ $underline }) => $underline ? `underline` : `none`};
`;
13. Styled Components 파헤치기
태그 함수란?
- 태그 함수(Tagged Function)는 ES6에서 도입된 템플릿 리터럴 문법을 사용하여 실행할 수 있는 함수입니다.
function h1(strings, ...values) {
return [strings, values];
}
const result = h1`color: pink;`;
console.log(result);
h1
함수는 첫 번째 파라미터로 strings
배열, 나머지 파라미터들을 values
배열로 받습니다.
- 이렇게 일반적인 형태로 함수를 선언하고 템플릿 리터럴로 실행하면 특정한 형태로 파라미터가 전달됩니다.
function h1(strings, ...values) {
return [strings, values];
}
const backgroundColor = 'black';
const result2 = h1`
background-color: ${backgroundColor};
color: pink;
`;
console.log(result2);
- 첫 번째 배열
strings
에는 템플릿 리터럴 내의 문자열들을 저장하고, values
에는 표현식의 값들을 저장합니다.
간단한 버전으로 Styled Components 따라하기
function h1(strings, ...values) {
function Component({ children, ...props }) {
let style = '';
for (let i = 0; i < strings.length; ++i) {
style += strings[i];
if (typeof values[i] === 'function') {
const fn = values[i];
style += fn(props);
} else if (values[i]) {
style += values[i];
}
}
const className = `my-sc-${style.length}`;
return (
<>
<style>{`.${className} {${style}}`}</style>
<h1 className={className}>{children}</h1>
</>
);
}
return Component;
}
const backgroundColor = 'black';
const StyledH1 = h1`
color: pink;
${({ dark }) => dark && 'background-color: black;'}
`;
function App() {
return <StyledH1 dark>Hello World</StyledH1>;
}
export default App;
StyledH1
컴포넌트는 CSS 코드를 생성하여 <style>
태그로 넣어주는 컴포넌트입니다.
- 이때 삽입된 값
${({ dark }) => dark && 'background-color: black;'}
은 함수이기 때문에, props를 아규먼트로 실행해서 CSS로 만듭니다.
dark
라는 자바스크립트 표현식도 background-color: black;
CSS 문자열로 반영됩니다.
Feedback
- styled-components 공식 문서로 공부하면 충분할 것 같다.
- Vite도 공부하자.
Reference