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;
Nesting에서 &는 부모 선택자를 의미한다.
위 코드에서는 버튼 컴포넌트의 클래스를 뜻하는 건데요. 기존 CSS 코드로 표현해 본다면, 버튼 컴포넌트가 .Button이라는 클래스 이름을 쓸 때 &:hover는 .Button:hover와 같은 의미다.
그럼 컴포넌트 안에 있는 또 다른 컴포넌트를 선택하는 법
Styled Components로 Icon과 StyledButton 컴포넌트를 각각 만들고, StyeldButton 안에 Icon을 배치할거다. 이때 StyledButton 컴포넌트 안에서 Icon 컴포넌트를 선택해 별도로 margin-right: 4px라는 속성을 지정하려고 한다.
이럴 경우, 컴포넌트를 선택자로 쓰고 싶을 때는 ${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;
특히, &와 자손 결합자를 사용하는 경우에는 &를 생략할 수 있다. 즉 ${Icon}만 써도 똑같이 동작한다. 보통 간편하게 많이 쓰니까, 자손 결합자로 Nesting 할 때는 아래처럼 쓰는 걸 권장한다.
const StyledButton = styled.button`
background-color: #6750a4;
border: none;
color: #ffffff;
padding: 16px;
${Icon} {
margin-right: 4px;
}
&:hover,
&:active {
background-color: #463770;
}
`;
참고로 Nesting은 여러 겹으로 할 수도 있다. 예를 들어서 아래 코드처럼 Nesting된 규칙 안에서 규칙을 만들 수도 있다.
const StyledButton = styled.button`
...
&:hover,
&:active {
background-color: #7760b4;
${Icon} {
opacity: 0.2;
}
}
`;
다음은 크기를 조절하는 size, 둥근 모양을 지정하는 round라는 Prop을 추가해 버튼 컴포넌트의 크기와 모양을 조절하는 예시이다
import styled from 'styled-components';
const SIZES = {
large: 24,
medium: 20,
small: 16,
};
const Button = styled.button`
background-color: #6750a4;
border: none;
border-radius: ${({ round }) => round ? `9999px` : `3px`};
color: #ffffff;
font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
padding: 16px;
&:hover,
&:active {
background-color: #463770;
}
`;
export default Button;
import styled from 'styled-components';
import Button from './Button';
const Container = styled.div`
${Button} {
margin: 10px;
}
`;
function App() {
return (
<Container>
<h1>기본 버튼</h1>
<Button size="small">small</Button>
<Button size="medium">medium</Button>
<Button size="large">large</Button>
<h1>둥근 버튼</h1>
<Button size="small" round>
round small
</Button>
<Button size="medium" round>
round medium
</Button>
<Button size="large" round>
round large
</Button>
</Container>
);
}
export default App;
달러와 중괄호(${ ... })를 사용해서 JavaScript 코드를 집어넣을 수 있다. 이런 걸 표현식 삽입법(Expression Interpolation)이라고 부른다. 표현식 삽입법을 사용하면 Styled Components에서 Prop에 따라 컴포넌트의 스타일을 다르게 보여줄 수 있다.
가장 기본적인 사용법은 JavaScript 변수를 그대로 넣는 방식이다.
아래 예시처럼 우리가 평소에 템플릿 문자열을 만들 때 사용하는 방식이라고 생각하면 된다.
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;
`;
다음으로, Prop에 따라 스타일을 다르게 적용하는 함수를 넣으려고 한다. 함수의 파라미터로는 Props를 받고, 리턴 값으로는 스타일 코드를 리턴하면 된다. 참고로 이건 템플릿 리터럴의 기능이 아니라 Styled Components가 내부적으로 처리해 준다
const SIZES = {
large: 24,
medium: 20,
small: 16
};
const Button = styled.button`
...
font-size: ${(props) => SIZES[props.size]}px;
`;
함수를 사용할 때 많이 사용하는 패턴 중 하나는 논리 연산자를 사용한다.
예를 들어서, round라는 Prop이 참일 때 컴포넌트의 모서리를 둥글게 만드는 예제를 살펴보자
const Button = styled.button`
...
${({ round }) => round && `
border-radius: 9999px;
`}
`;
마찬가지로 자주 쓰는 패턴이다. round 가 참이면 완전히 둥근 모서리를 보여주고, 거짓이면 3px 정도로 살짝 부드럽게 깎인 모서리를 보여주고 싶다면 아래와 같이 삼항 연산자로 쓸 수 있다.
border-radius: ${({ round }) => round ? `9999px` : `3px`};
다른 컴포넌트의 스타일을 가져와서 원하는 대로 사용하는 방법
스타일 상속을 하려면 styled() 함수를 사용하면 되는데, styled.tagname으로 만든 컴포넌트는 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;
SubmitButton 컴포넌트의 코드를 보면 style(Button)을 통해 Button 컴포넌트를 상속하고 있다.
즉, 예시 코드에서 SubmitButton도 Button 의 스타일 전부를 상속하고, 몇 가지 스타일만 추가해 원하는 컴포넌트를 만들고 있다
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>
);
}
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;
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;
`;
다음과 같이 Input,Button 컴포넌트 css 요소가 곂칠때 아래와 같이 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라는 태그 함수를 붙여서 쓴다는 점을 주의 깊게 봐야한다. 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);
`;
글로벌 스타일 컴포넌트를 최상위 컴포넌트에서 렌더링 하면 글로벌 스타일이 항상 적용된 상태가 되도록 할 수 있다.
예를 들어서 사이트 전체에 폰트와 box-sizing을 지정하는 코드를 작성해 보았다.
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;
연속으로 보여주는 한 장 한 장의 이미지를 프레임이라고 한다
움직임의 기준이 되는 프레임만 만들고 그 사이의 프레임들을 자동으로 채워 넣는 방식을 주로 사용합니다. 이때 '움직임의 기준이 되는 프레임'을 '키프레임'이라고 부릅니다.
플레이스홀더 애니메이션을 HTML/CSS 코드로 간단히 만들어보면 아래와 같다
html
<div class="placeholder">
<div class="placeholder-item a"></div>
<div class="placeholder-item b"></div>
<div class="placeholder-item c"></div>
</div
css
@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;
}
위와 같은 코드를 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;