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