npm install styled-components
&
선택자&
선택자를 사용해서 앞에서 만든 버튼 컴포넌트를 호버하거나 클릭했을 때 배경색이 바뀌게👇🏻👇🏻
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5426&directory=4-1.gif&name=4-1.gif
src/Button.js
import styled from 'styled-components';
const Button = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
&:hover,
&:active {
background-color: #463770;
}
`;
export default Button;
&
는 부모 선택자를 의미한다..Button
이라는 클래스 이름을 쓸 때 &:hover
는 .Button:hover
와 같은 의미
.Button {
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
}
.Button:hover,
.Button:active {
background-color: #463770;
}
Styled Components에선 클래스 이름을 쓰지 않는다. 그럼 컴포넌트 안에 있는 또 다른 컴포넌트를 선택하고 싶으면 어떻게 해야 할까?
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5426&directory=4-2.gif&name=4-2.gif
${Icon}
같이 컴포넌트 자체를 템플릿 리터럴 안에 넣어주면 된다.
import styled from 'styled-components';
import nailImg from './nail.png';
const Icon = styled.img`
width: 16px;
height: 16px;
`;
const StyledButton = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
& ${Icon} {
margin-right: 4px;
}
&:hover,
&:active {
background-color: #463770;
}
`;
function Button({ children, ...buttonProps }) {
return (
<StyledButton {...buttonProps}>
<Icon src={nailImg} alt="nail icon" />
{children}
</StyledButton>
);
}
export default Button;
자손 결합자(Descendant Combinator)로 쓴 & ${Icon} { ... }
부분을 기존 CSS로 표현해 본다면 아래처럼 나타낼 수 있다.
.StyledButton {
...
}
.StyledButton .Icon {
margin-right: 4px;
}
특히, &
와 자손 결합자를 사용하는 경우에는 &
를 생략할 수 있다. 즉 ${Icon}
만 써도 똑같이 동작한다. .
const StyledButton = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
${Icon} {
margin-right: 4px;
}
&:hover,
&:active {
background-color: #463770;
}
`;
참고로 Nesting은 여러 겹으로 할 수도 있다.
const StyledButton = styled.button`
...
&:hover,
&:active {
background-color: #7760b4;
${Icon} {
opacity: 0.2;
}
}
`;
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5426&directory=4-3.gif&name=4-3.gif
&:hover, &:active { ... }
안에 있는 ${Icon}
선택자를 CSS 코드로 표현해 보면
.StyledButton:hover .Icon,
.StyledButton:active .Icon {
opacity: 0.5;
}
${ ... }
안에 값(변수) 사용하기const a = 1;
const b = 2;
const str = `${a} 더하기 ${b}는 ${a + b} 입니다.`;
아래 예시 코드에서 ${SIZES['medium']}
부분은 숫자 20을 뜻하기 때문에, font-size: ${SIZES['medium']}px;
는 font-size: 20px;
란 코드가 된다.
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
...
font-size: ${SIZES['medium']}px;
`;
${ ... }
안에 함수 사용하기
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
...
font-size: ${(props) => SIZES[props.size]}px;
`;
만약에 구조 분해(Destructuring)하면 아래처럼 쓸 수 있다.
font-size: ${({ size }) => SIZES[size]}px;
size
Prop이 값이 없거나 잘못된 값이면 어떻게 될까?
Styled Components에서는 undefined
값을 빈 문자열로 처리해 주기 때문에 font-size: px
같은 잘못된 CSS 코드가 된다. 그래서 가능하면 기본 값을 정해주는 게 좋다. (널 병합 연산자 쓰기..)
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
함수를 사용할 때 많이 사용하는 패턴 중 하나는 논리 연산자를 사용하는 것.
예를 들어서, round
라는 Prop이 참일 때 컴포넌트의 모서리를 둥글게 만듦
const Button = styled.button`
...
${({ round }) => round && `
border-radius: 9999px;
`}
`;
round
값이 참이면 그 뒤에 값까지 계산하기 때문에 border-radius: 9999px
이라는 문자열이 리턴돼서 적용된다. 반대로, round
값이 거짓이면 그냥 false
가 리턴돼서 아무런 값도 적용되지 않는다.
round
가 참이면 완전히 둥근 모서리를 보여주고, 거짓이면 3px
정도로 살짝 부드럽게 깎인 모서리를 보여주고 싶다면 아래와 같이 삼항 연산자로 쓸 수 있다.
border-radius: ${({ round }) => round ? `9999px` : `3px`};
HTML 태그에 스타일링하는 건 styled.tagname
을 사용해서 할 수 있다. 그런데, JSX 문법으로 직접 만든 컴포넌트나, Styled Components를 사용해 이미 만들어진 다른 컴포넌트에 스타일을 입히려면 어떻게 해야 할까? → 상속
styled()
함수Styled Components로 만들어진 컴포넌트를 상속하려면 styled()
함수를 사용하면 된다.
src/Button.js
import styled from 'styled-components';
const SIZES = {
large: 24,
medium: 20,
small: 16,
};
const Button = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
padding: 16px;
${({ round }) =>
round
? `
border-radius: 9999px;
`
: `
border-radius: 3px;
`}
&:hover,
&:active {
background-color: #463770;
}
`;
export default Button;
src/App.js
import styled from 'styled-components';
import Button from './Button';
const SubmitButton = styled(Button)`
background-color: #de117d;
display: block;
margin: 0 auto;
width: 200px;
&:hover {
background-color: #f5070f;
}
`;
function App() {
return (
<div>
<SubmitButton>계속하기</SubmitButton>
</div>
);
}
export default App;
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5431&directory=9-1.png&name=9-1.png
Button
컴포넌트의 스타일을 상속해서 새로운 버튼 SubmitButton
을 만들고, App
컴포넌트 안에 SubmitButton
을 배치하고 있다.
styled(Button)
SubmitButton
이 Button
의 스타일을 상속받게 된다. Button
컴포넌트에 SubmitButton
의 스타일이 상속됐기 때문에, 마찬가지로 글씨는 흰색이다.
styled()
사용하기styled.tagname
으로 만든 컴포넌트는 바로 styled()
함수를 사용할 수 있지만, 그렇지 않은 컴포넌트는 따로 처리가 필요하다.
예시) 약관을 보여주는 TermsOfService
라는 컴포넌트가 있으면
src/TermsOfService.js
function TermsOfService() {
return (
<div>
<h1>㈜코드잇 서비스 이용약관</h1>
<p>
환영합니다.
<br />
Codeit이 제공하는 서비스를 이용해주셔서 감사합니다. 서비스를
이용하시거나 회원으로 가입하실 경우 본 약관에 동의하시게 되므로, 잠시
시간을 내셔서 주의 깊게 살펴봐 주시기 바랍니다.
</p>
<h2>제 1 조 (목적)</h2>
<p>
본 약관은 ㈜코드잇이 운영하는 기밀문서 관리 프로그램인 Codeit에서
제공하는 서비스를 이용함에 있어 이용자의 권리, 의무 및 책임사항을
규정함을 목적으로 합니다.
</p>
</div>
);
}
export default TermsOfService;
TermsOfService
는 JSX 문법을 직접 사용해서 바로 컴포넌트가 만들어졌다.
이 컴포넌트를 styled()
함수로 감싸면
src/App.js
import styled from 'styled-components';
import Button from './Button';
import TermsOfService from './TermsOfService';
const StyledTermsOfService = styled(TermsOfService)`
background-color: #ededed;
border-radius: 8px;
padding: 16px;
margin: 40px auto;
width: 400px;
`;
const SubmitButton = styled(Button)`
background-color: #de117d;
display: block;
margin: 0 auto;
width: 200px;
&:hover {
background-color: #f5070f;
}
`;
function App() {
return (
<div>
<StyledTermsOfService />
<SubmitButton>계속하기</SubmitButton>
</div>
);
}
export default App;
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5431&directory=9-2.png&name=9-2.png
styled()
로 지정한 스타일이 적용되지 않는다.
StyledTermsOfService
에 지정한 배경색이랑 너비가 적용이 안 된 거 같다. 왜 그럴까?
Styled Components는 내부적으로 className
을 따로 생성합니다. 그리고, 자체적으로 생성된 className
이 있는 부분에 styled()
함수의 스타일이 입혀진다.
그런데, JSX 문법으로 직접 만든 컴포넌트는 styled()
함수가 적용될 className
에 대한 정보가 없다. styled()
함수에서 지정한 스타일이 입혀질 부분이 어딘지 알 수 없으니 스타일이 적용되지 않은 것이다.
이렇게, Styled Components를 사용하지 않고 직접 만든 컴포넌트는 className
값을 Prop으로 따로 내려줘야 styled()
함수를 사용할 수 있다.
src/TermsOfService.js
function TermsOfService({ className }) {
return (
<div className={className}>
...
</div>
);
}
https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=5431&directory=9-3.png&name=9-3.png
직접 만든 컴포넌트에 className
Prop을 따로 내려주는 건 syled()
함수가 적용될 부분의 className
을 별도로 정해주는 거라고 이해하자.
위 코드의 경우엔, <div>
태그에 className
을 내려줬기 때문에 styled(TermsOfService)
에서 작성한 코드는 TermsOfService
안에 있는 <div>
태그에 적용된다.
정리하자면, 스타일 상속을 하려면 styled()
함수를 사용하면 되는데, styled.tagname
으로 만든 컴포넌트는 styled()
함수로 바로 상속하면 되고, Styled Components를 사용하지 않고 직접 만든 컴포넌트에는 클래스 이름을 내려준 후에 styled()
함수로 상속해야 한다.
가끔 중복되는 CSS 코드들을 변수처럼 저장해서 여러 번 다시 사용하고 싶을 때가 있다. → css 함수
Button
컴포넌트와 Input
컴포넌트에 같은 글자 크기를 갖도록 하는 상황을 생각해보자.
size
라는 Prop으로 small
, medium
, large
각각에 지정된 크기를 전달하면 16, 20, 24 픽셀로 글자 크기를 지정하려 한다. 가장 단순한 방법은 아래처럼 똑같은 코드를 두 번 작성하는 형태가 될 것이다.
import styled from 'styled-components';
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
...
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
`;
const Input = styled.input`
...
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
`;
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
라는 태그 함수를 붙여서 쓴다는 점!!!
Props를 받아서 사용하는 함수가 들어있기 때문에 반드시 css
함수를 사용해야 한다.
만약에 아래 코드처럼 함수를 삽입하지 않는 단순한 문자열이라면 일반적인 템플릿 리터럴을 써도 된다.
const boxShadow = `
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
`;
하지만, 이런 경우에도 항상 css
함수를 사용하도록 습관화하는 걸 권장 드립니다.
const boxShadow = css`
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
`;
CSS를 작성하다 보면, 모든 컴포넌트에 적용하고 싶은 코드가 생기는 경우가 있다. ( 폰트나 box-sizing: border-box
)
→ 글로벌 스타일 컴포넌트를 사용하자. 글로벌 스타일 컴포넌트를 최상위 컴포넌트에서 렌더링 하면 글로벌 스타일이 항상 적용된 상태가 되도록 할 수 있다.
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
이라는 함수는 다른 Styled Components 함수들과 마찬가지로 템플릿 리터럴 문법으로 사용한다. 이 함수는 <style>
태그를 컴포넌트로 만드는 것이다. 실제로 <style>
태그가 저 위치에 생기는 건 아니고, Styled Components가 내부적으로 처리해서 <head>
태그 안에 우리가 작성한 CSS 코드를 넣어 준다.
영상과 애니메이션은 여러 개의 사진을 연속으로 보여주면서 마치 움직이는 듯한 효과를 만들어 낸다. 이때 연속으로 보여주는 한 장 한 장의 이미지를 프레임이라고 한다. 이때 '움직임의 기준이 되는 프레임'을 '키프레임'이라고 부른다.
CSS에서 키프레임은 CSS 애니메이션을 만들 때 기준이 되는 지점을 정하고, 적용할 CSS 속성을 지정하는 문법을 뜻한다.
예시) 아래 HTML/CSS 코드는 .ball
이라는 <div>
태그를 위아래로 움직이는 애니메이션이다. 시작 지점에서는 기본값인 translateY(0%)
를 적용하고, 애니메이션의 중간 시점에서는 translateY(100%)
를 적용한 다음, 마지막에는 기본값인 translateY(0%)
을 적용하고 있다.
<div class="ball"></div>
@keyframes bounce {
0% {
transform: translateY(0%);
}
50% {
transform: translateY(100%);
}
100% {
transform: translateY(0%);
}
}
.ball {
animation: bounce 1s infinite;
background-color: #ff0000;
border-radius: 50%;
height: 50px;
width: 50px;
}
@keyframes
로 키프레임 애니메이션을 선언한 다음에, animation
속성에서 이름으로 쓰고 있다.
keyframes
함수Styled Components에서는 키프레임 애니메이션을 어떻게 넣을 수 있을까?
플레이스홀더 애니메이션은 사이트에서 보여줄 내용을 로딩하는 동안 내용이 들어갈 자리에 미리 네모나 동그라미 같은 걸 보여주면서, 애니메이션으로 로딩 중이라는 걸 보여주는 것이다.
플레이스홀더 애니메이션을 HTML/CSS 코드로 간단히 만들어보면 아래와 같다.
<div class="placeholder">
<div class="placeholder-item a"></div>
<div class="placeholder-item b"></div>
<div class="placeholder-item c"></div>
</div
@keyframes placeholder-glow {
50% {
opacity: 0.2;
}
}
.placeholder {
animation: placeholder-glow 2s ease-in-out infinite;
}
.placeholder-item {
background-color: #888888;
height: 20px;
margin: 8px 0;
}
.a {
width: 60px;
height: 60px;
border-radius: 50%;
}
.b {
width: 400px;
}
.c {
width: 200px;
}
여기서 placeholder-glow
라는 애니메이션 코드는 애니메이션의 중간인 50% 시점에 0.2의 불투명도로 만드는 것이다. 불투명도의 기본값이 1이니까, 불투명도가 0.2로 낮아졌다가 다시 1로 높아지는 애니메이션이 된다.
Styled Components 버전)
@keyframes
는 keyframes
라는 함수를 쓰면 된다. styled
함수와 마찬가지로 템플릿 리터럴로 사용하는 태그 함수다. 여기서 특히 keyframes
로 만든 애니메이션을 ${placeholderGlow}
처럼 템플릿 리터럴에 삽입하는 형태로 사용했다는 점!!
src/Placeholder.js
import styled, { keyframes } from 'styled-components';
const placeholderGlow = keyframes`
50% {
opacity: 0.2;
}
`;
export const PlaceholderItem = styled.div`
background-color: #888888;
height: 20px;
margin: 8px 0;
`;
const Placeholder = styled.div`
animation: ${placeholderGlow} 2s ease-in-out infinite;
`;
export default Placeholder;
src/App.js
import styled from 'styled-components';
import Placeholder, { PlaceholderItem } from './Placeholder';
const A = styled(PlaceholderItem)`
width: 60px;
height: 60px;
border-radius: 50%;
`;
const B = styled(PlaceholderItem)`
width: 400px;
`;
const C = styled(PlaceholderItem)`
width: 200px;
`;
function App() {
return (
<div>
<Placeholder>
<A />
<B />
<C />
</Placeholder>
</div>
);
}
export default App;
참고로, keyframes
함수가 리턴하는 변수는 단순한 문자열이 아니라 JavaScript 객체다. 리턴되는 값이 이런 객체이기 때문에 반드시 styled
함수나 css
함수를 통해 사용해야 한다!
{
id: "sc-keyframes-bEnYbJ"
inject: ƒ (e, t)
name: "bEnYbJ"
rules: "\n 50% {\n opacity: 0.2;\n }\n"
toString: ƒ ()
}
ThemeProvider
로 테마 설정 사용하기테마 기능을 만들기 위해서는 현재 테마로 설정된 값을 사이트 전체에서 참조할 수 있어야 한다. React에서는 이런 상황에서 Context라는 걸 사용한다. Styled Components에서도 Context를 기반으로 테마를 사용할 수 있다. Context를 내려주는 컴포넌트로 ThemeProvider
라는 걸 사용하면 된다.
App.js
import { ThemeProvider } from "styled-components";
import Button from "./Button";
function App() {
const theme = {
primaryColor: '#1da1f2',
};
return (
<ThemeProvider theme={theme}>
<Button>확인</Button>
</ThemeProvider>
);
}
export default App;
ThemeProvider
라는 Context Provider를 사용해서 theme
이라는 객체를 내려준다. 이렇게 하면 ThemeProvider
안에 있는 Styled Components로 만든 컴포넌트에서는 Props를 사용하듯이 theme
이라는 객체를 쓸 수 있다.
예를 들어서 Button
컴포넌트에서 theme
값을 써 볼게요. Prop 값을 사용하듯이 theme
이라는 값을 쓰면 된다. 기존에 있던 배경색 대신에 아래처럼 함수를 삽입해서 테마 값을 사용한다.
src/Button.js
const Button = styled.button`
background-color: ${({ theme }) => theme.primaryColor};
/* ... */
`;
만약에 여러 테마를 선택하게 하고 싶다면 useState
를 활용해서 테마를 바꿔주면 된다.
import { useState } from 'react';
import { ThemeProvider } from 'styled-components';
import Button from './Button';
function App() {
const [theme, setTheme] = useState({
primaryColor: '#1da1f2',
});
const handleColorChange = (e) => {
setTheme((prevTheme) => ({
...prevTheme,
primaryColor: e.target.value,
}));
};
return (
<ThemeProvider theme={theme}>
<select value={theme.primaryColor} onChange={handleColorChange}>
<option value="#1da1f2">blue</option>
<option value="#ffa800">yellow</option>
<option value="#f5005c">red</option>
</select>
<br />
<br />
<Button>확인</Button>
</ThemeProvider>
);
}
export default App;
그런데, 만약 테마 설정 페이지를 만든다고 하면 테마 값을 일반적인 컴포넌트에서 참조할 필요도 생길 것이다. 그럴 때는 ThemeContext
를 불러오면 된다. 이 값은 React Context이기 때문에 useContext
로 쓴다.
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
// ...
function SettingPage() {
const theme = useContext(ThemeContext); // { primaryColor: '#...' }
}
사이트를 개발하다보면 모양은 버튼이지만 역할은 링크인 경우가 있다. 예를 들어 페이스북의 로그인 페이지에 있는 "Create new account" 버튼은 모양은 버튼이지만 "Log In"이랑 달리 <a>
태그로 되어 있고, 클릭하면 회원가입 페이지로 이동한다.
버튼의 스타일 코드는 버튼 컴포넌트에 있을텐데, 이걸 <a>
태그 버전으로도 만들어야 하는 걸까? 이렇게 반복되는 스타일링 코드를 어떻게 관리하면 좋을까?
이럴 때 간편하게 사용할 수 있는게 바로 as
라는 Prop 이다.
예를 들어서 아래와 같이 Button
이라는 컴포넌트가 <button>
태그로 만들어져 있을 때, 이걸 <a>
태그로 바꿔서 사용할 수 있다.
const Button = styled.button`
/* ... */
`;
as
로 태그 이름을 내려주면 해당하는 태그로 사용할 수 있다. 굳이 버튼 모양의 링크 컴포넌트를 만들 필요가 없다!!
<Button href="https://example.com" as="a">
LinkButton
</Button>
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://localhost:3000/static/js/bundle.js:26:5)
at O (http://localhost:3000/static/js/bundle.js:44495:6)
at App
HTML 태그에 underline
이라는 속성을 지정했는데, 그 속성의 값이 문자열이 아니라서 생긴 오류다. <a>
태그에는 underline
이라는 속성이 없다.
이 문제의 근본적인 원인은 <a {...props} className={className}>
이 부분이다. Spread를 하는 과정에서 의도하지 않은 underline
Prop까지 내려간 것이 원인이다.
underline
Prop이 전달되는 순서를 정리해 보면…
StyledLink
컴포넌트에서 underline
이라는 Prop을 받는다StyledLink
가 스타일링하고 있는 Link
컴포넌트에 underline
Prop이 전달된다Link
컴포넌트에서 Spread 문법을 통해 <a>
태그에 underline
Prop이 전달된다.이럴 때는 구조 분해 코드를 조금 고쳐서 underline
을 제외하면 원치 않는 Prop이 전달되는 걸 막을 수 있다.
function Link({ className, children, underline, ...props }) {
return (
<a {...props} className={className}>
{children}
</a>
);
};
underline
이라는 Prop은 Link
에서 쓰려고 만든 게 아니라 StyledLink
컴포넌트에서만 쓰려고 만든 건데, Link
에 Prop으로 전달되는게 좀 더 근본적인 문제인 거 같다.
이럴 때 아예 Prop이 전달되지 않게 만드는 방법이 있다. 바로 Transient Prop이라는 걸 사용하는 것이다.
Transient(일시적) Prop을 사용하면 Styled Components로 스타일링하는 컴포넌트에서만 Prop을 사용하고, 스타일링의 대상이 되는 컴포넌트에는 Prop이 전달되지 않도록 할 수 있다.
StyledLink
컴포넌트 안에서만 Prop을 사용하고 Link
에는 전달하지 않는 예시)
Transient Prop을 만들려면 앞에다 $
기호를 붙여주면 된다. 아래 코드에서 $underline
Prop은 StyledLink
안에서만 사용되고, Link
로 전달되지는 않는다.
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;
태그 함수(Tagged Function)는 템플릿 리터럴 문법을 사용해서 실행할 수 있는 함수다. .
function h1(strings, ...values) {
return [strings, values];
}
const result = h1`color: pink;`;
console.log(result); // [['color: pink;'], []]
h1
이라는 함수는 첫 번째 파라미터로 strings
, 그리고 나머지 파라미터들을 values
배열로 받는다. 이 함수를 템플릿 리터럴로 실행했다. 이렇게 일반적인 형태로 함수를 선언하고, 템플릿 리터럴로 실행하면 특정한 형태로 파라미터가 전달된다.
console.log
로 출력하면 strings
에는 입력한 문자열이 배열로 나온다. values
는 빈 배열로 출력된다.
템플릿 리터럴에 값을 삽입하고 출력하면)
function h1(strings, ...values) {
return [strings, values];
}
const backgroundColor = 'black';
const result2 = h1`
background-color: ${backgroundColor};
color: pink;
`;
console.log(result2);
// [['\n background-color: ', ';\n color: pink;\n'], ['black']]
strings
에는 값이 삽입되는 부분 앞뒤의 문자열들이 잘려서 배열로 들어가 있고, values
에는 삽입된 값들이 배열로 들어가 있다. 이것이 태그 함수의 기본적인 동작이다. 이걸 사용해서 CSS 스타일이 생성된 리액트 컴포넌트를 만드는 것이 Styled Components의 핵심 아이디어다.
간단하게 <style>
태그를 렌더링하는 컴포넌트를 만들어 보자.
function h1(strings, ...values) {
// React 컴포넌트를 만든다
function Component({ children }) {
// 템플릿 리터럴에서 받은 값을 CSS 코드로 만든다
let style = '';
for (let i = 0; i < strings.length; ++i) {
style += strings[i];
if (values[i]) {
style += values[i];
}
}
// CSS 코드에 따라 클래스 이름을 만든다
const className = `my-sc-${style.length}`;
// `<style>` 태그로 만든 CSS 코드를 렌더링한다
return (
<>
<style>{`.${className} {${style}}`}</style>
<h1 className={className}>{children}</h1>
</>
);
}
return Component;
}
const backgroundColor = 'black';
const StyledH1 = h1`
background-color: ${backgroundColor};
color: pink;
`;
function App() {
return <StyledH1>Hello World</StyledH1>;
}
export default App;
태그 함수 안에서 컴포넌트를 만들고 이걸 리턴하는 것이다. 이 컴포넌트는 우리가 태그 함수에서 집어넣은 CSS 코드를 <style>
태그에 렌더링하는 컴포넌트다.
Styled Components에서는 내부적으로 클래스네임을 알아서 생성해주기 때문에 우리가 클래스 이름을 신경 쓸 필요가 없다.
함수를 삽입하는 예시)
단순히 strings
와 values
배열을 합쳐주는 게 아니라, React 컴포넌트의 Props를 활용하는 함수가 삽입되는 경우를 처리할 것임.
function h1(strings, ...values) {
// React 컴포넌트를 만든다
function Component({ children, ...props }) {
// 템플릿 리터럴에서 받은 값을 CSS 코드로 만든다
let style = '';
for (let i = 0; i < strings.length; ++i) {
style += strings[i];
// 삽입된 값이 함수이면 props를 가지고 실행한 값을 CSS에 넣는다.
if (typeof values[i] === 'function') {
const fn = values[i];
style += fn(props);
// 그 외에 값이 존재하면 CSS에 문자열로 넣는다.
} else if (values[i]) {
style += values[i];
}
}
// CSS 코드에 따라 클래스 이름을 만든다
const className = `my-sc-${style.length}`;
// `<style>` 태그로 만든 CSS 코드를 렌더링한다
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;
Component
함수 안에서 CSS 코드를 생성하는 부분에 함수를 처리하는 부분이 추가되었다.
h1
을 실행해서, StyledH1
이라는 컴포넌트가 만들어진다.App
컴포넌트를 렌더링하면 StyledH1
컴포넌트도 렌더링한다.StyledH1
컴포넌트에서는 CSS 코드를 생성해서 <style>
태그로 넣는다. 이때 함수로 삽입된 값(${({ dark }) => dark && 'background-color: black;'}
부분)은 함수이기 때문에, Props를 가지고 실행해서 CSS로 만든다. 우리 코드에서는 dark
라는 값이 있기 때문에, CSS에는 background-color: black;
이라는 값으로 반영된다.