1. CSS-in-JS
CSS-in-JS는 js 파일 안에서 css 코드를 작성하기 때문에 css의 변수와 함수를 그대로 사용할 수 있습니다. 그리고 css 클래스명을 해시 값으로 자동으로 생성하여 클래스 이름 작명에 대한 고민의 수고가 덜합니다. 또한, 현대 웹은 컴포넌트 기반으로 발전해 나가기 때문에 컴포넌트와 스타일을 하나의 파일에서 작성하는 CSS-in-JS는 모듈화가 수월해집니다.
$ npm install styled-components
css, Sass에서는 별도의 css, sass 파일을 생성하여 js파일에 import해서 사용했지만, 스타일드 컴포넌트는 스타일링을 할 때 ES6의 Tagged Templeate Literal 문법을 이용하여 js파일 안에서 선언해서 사용할 수 있습니다. 아래의 예시를 통해서 스타일드 컴포넌트의 기본적인 문법에 대해서 알아보도록 하겠습니다.
// App.js
import React from 'react';
import styled from 'styled-components'; // 1
const App = () => {
return <Title>styled-components!!</Title>; // 2
};
const Title = styled.h1` ⌉
font-size: 32px; |
text-align: center; | // 3
color: purple; |
`; ⌋
export default App;
styled-compoents
를 사용하기 위해서 스타일드 컴포넌트의 styled를 import 해줍니다.ex)
<LogoImage src="/images/logo.png" alt="로고" />
const [컴포넌트명] = styled.[html태그]`
[부여하고자 하는 css속성]
`;
3. 스타일드 컴포넌트의 활용
동적으로 스타일을 적용하기 위해서 가장 많이 사용되는 활용 방법입니다. 부모에서 자식컴포넌트에 값을 넘겨줄 때 props를 사용한 것처럼, 이를 활용하여 조건에 따른 스타일링을 할 수 있습니다. <컴포넌트 속성명=값 />
의 형태로 선언해 줍니다. 그러면, ${(props)⇒props.속성명}
으로 값을 유동적으로 사용할 수 있습니다. 예를 들어 <Button color="red" />
로 선언하면, props.color
의 값은 red
가 들어오게 됩니다.
이렇게 받은 props 값은 아래와 같은 방법으로 동적으로 스타일링을 할 수 있습니다.
// App.js
import React, { useState } from 'react';
import styled, { css } from 'styled-components';
const App = () => {
const [changeColor, setChangeColor] = useState(false);
const handleChanageColor = () => {
setChangeColor(!changeColor);
};
return (
<>
<Button onClick={handleChanageColor} primary={changeColor}>
Button
</Button>
</>
);
}
const Button = styled.button`
padding: 20px;
margin: 10px;
border: none;
background-color: ${(props) => (props.primary ? "purple" : "pink")}; // 1
`;
export default App;
스타일드 컴포넌트의 상속 기능을 이용한다면, 이미 선언된 스타일드 컴포넌트를 활용해서 새로운 스타일드 컴포넌트를 만들 수도 있습니다.
const [새로운 컴포넌트명] = styled(기존 컴포넌트명)`
[부여하고자 하는 css속성]
`;
아래의 예시를 통해 'Button' 컴포넌트를 활용하여 'NewButton' 컴포넌트를 구성하는 방법을 알아보겠습니다.
// App.js
const App = () => {
return (
<>
<Button>Button</Button>
<NewButton>New Button</NewButton>
</>
);
};
const Button = styled.button`
margin: 20px;
padding: 20px;
border: none;
background-color: yellow;
font-size: 20px;
`;
const NewButton = styled(Button)`
color: white;
background-color: purple;
`;
export default App;
폰트 컬러와 백그라운드 컬러만 다른 두 가지의 버튼을 만든다고 가정해보겠습니다. 이때 Button 컴포넌트의 기본적인 스타일을 적용시키기 위해서 const NewButton = styled(Button)
[스타일];
와 같이 상속하여 적용합니다. 즉, NewButton 컴포넌트에는 Button 컴포넌트에 적용한 스타일이 그래도 상속되어 NewButton컴포넌트에서 추가로 적용한 폰터 컬러, 백그라운드 컬러 이외에는 같은 스타일이 적용됩니다.
// Elements
<button class="sc-bczRLJ jgCBVB">Button</button> // 1
<button class="sc-bczRLJ sc-gsnTZi jgCBVB hXQPTY">New Button</button> // 2
그리고 위의 예시처럼 같은 파일에서 선언한 스타일드 컴포넌트뿐만 아니라, Link, 아이콘 라이브러리 등 외부 라이브러리의 컴포넌트도 import 하면 아래의 예시처럼 스타일을 확장해서 사용할 수 있습니다.
// App.js
import { Link } from 'react-router-dom';
;import styled from 'styled-components';
const App = () => {
return <CartLink to="/cart">장바구니</CartLink>;
};
const CartLink = styled(Link)`
color: red;
`;
export default App;
// Elements
<a class="sc-eCYdqJ gubPie" href="/cart">장바구니</a>
3-3. 네스팅
스타일드 컴포넌트에서도 아래와 같이 네스팅해서 스타일을 적용할 수 있습니다. 하지만 스타일 컴포넌트는 컴포넌트를 분리해서 모듈화하기 위해 최적화되어있기 때문에, 네스팅을 적용해서 사용하는 것보다 하나의 컴포넌트로 분리해서 사용하는 것이 더 좋습니다.
import React from 'react';
import styled from 'styled-components';
const Main = () => {
return (
<List>
<li>
메뉴<a href="http://list">클릭</a>
</li>
</List>
);
};
const List = styled.ul`
padding: 0;
li {
padding: 10px 0;
color: red;
font-size: 30px;
a {
color: green;
}
}
`;
export default Main;
하지만 스타일드 컴포넌트를 사용하다 보면 네스팅을 사용해야 하는 때도 있습니다. react-slick과 같은 외부 라이브러리를 사용하여 이미 정해진 스타일을 변경해야 할 때는 네스팅을 이용해서 스타일링을 해야 합니다.
import React from 'react';
import styled from 'styled-components';
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
const Main = () => {
return (
<Carousel>
// slick 코드
</Carousel>
);
};
const Carousel = styled(Slider)`
.slick-slide {
// slick 커스텀 스타일
}
`;
export default Main;
4. 공통스타일
컴포넌트 각각에 적용해주면 좋은 css 스타일이 있지만 common.css, common.scss 처럼 전역에 공통으로 css 스타일을 적용해주는 때도 있습니다. 스타일드 컴포넌트에서는 createGlobalStyle 함수를 통해 전역에 적용하기 위한 스타일 컴포넌트를 생성할 수 있습니다.
// src/styles/GlobalStyle.js
import React from 'react'
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
font-family: 'Noto Sans KR', sans-serif;
}
`
export default GlobalStyle;
또한, 아래의 예시처럼 createGlobalStyle 함수로 생성한 GlobalStyle 컴포넌트를 전역스타일을 적용해주고 싶은 컴포넌트 상위에 적용해주면 GlobalStyle 컴포넌트 하위의 컴포넌트에는 전역스타일이 적용됩니다.
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import Router from './Router';
import GlobalStyle from './styles/GlobalStyle';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider>
<GlobalStyle />
<Router />
</ThemeProvider>
);
브라우저마다 설정되어있는 css 기본 스타일을 초기화해주기 위해 reset.css, reset.scss 파일을 생성해서 import 해주어 사용했습니다. css 기본 스타일을 초기화해주는 작업을 스타일드 컴포넌트에서는 styled-reset 라이브러리를 설치한 후 GlobalStyle.js 파일에 선언하여 전역에 적용할 수 있습니다.
$ npm install styled-reset
createGlobalStyle``;안에 ${reset}을 선언해 줍니다.
// src/styles/GlobalStyle.js
import React from 'react';
import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';
const GlobalStyle = createGlobalStyle`
${reset}
* {
box-sizing: border-box;
font-family: 'Do Hyeon', sans-serif;
}
`;
export default GlobalStyle;
sass에서는 변수와 mixin 등 공통으로 사용할 스타일에 대해서 파일을 만들고, 사용하고자 하는 scss파일에 import를 해서 적용을 시켰습니다. 하지만 매번 import 를 해주고, 참조해야 하는 css파일이 많아지면 의존성을 관리하기도 힘들어집니다. 이런 문제들을 해결하기 위해서 스타일드 컴포넌트에서는 ThemeProvider를 통해 전역으로 테마, JS변수 등을 공유하여 사용합니다.
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/GlobalStyle';
import theme from './styles/theme';
import Router from './Router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router />
</ThemeProvider>
);
ThemeProvider는 위의 예시와 같이 프로젝트의 엔트리 포인트인 index.js의 최초로 렌더링 되는 컴포넌트를 감싸주어서 모든 컴포넌트의 prop에 theme을 부여하여 적용할 수 있습니다.
color, fontSize등 공통된 테마는 theme.js에서 선언하여 각 컴포넌트에서 props로 받아 스타일을 적용할 수 있습니다. ThemeProvider의 속성으로 넘겨주면 전역에서 사용할 수 있습니다.
// theme.js
const theme = {
black: '#000000',
white: '#FFFFFF',
lightGrey: '#B0B0B0',
middleGrey: '#717171',
deepGrey: '#222222',
hoverGrey: '#DBDBDB',
};
export default theme;
// App.js
import { Link } from 'react-router-dom';
import styled from 'styled-components';
const App = () => {
return <Container>title</Container>;
};
const Container = styled.div`
background-color: ${props => props.theme.lightGrey}
`;
전역적으로 사용하기 위한 theme.js 파일을 만들고, 해당 파일을 index.js에서 ThemeProvider를 통해 설정해 주면, 위의 예시처럼 props로 받아와서 사용할 수 있는데 어떤 형태로 들어오는지 console.log 로 같이 확인해 보도록 하겠습니다. 그러면 props는 다음과 같은 객체 형태로 데이터가 들어오게 됩니다. 그래서 props.theme.ligthrgrey로 값을 사용할 수가 있게 됩니다.
// App.js
const Container = styled.div`
background-color: ${props => console.log(props)}
`;
// console.log(props)의 결과
props: {
children: 'title',
theme: {
black: '#000000',
white: '#FFFFFF',
lightGrey: '#B0B0B0',
middleGrey: '#717171',
deepGrey: '#222222',
hoverGrey: '#DBDBDB',
},
},
자주 사용하는 css 스타일에 대해서는 variables.js 파일을 별도로 생성하여 사용하는 게 좋습니다. variables.js 파일은 theme과 variables를 Theme Provider에 같이 prop으로 합쳐서 전역에서 사용하거나, 사용하고자 하는 파일에서만 import해서 mixin을 사용할 수 있습니다. 아래와 같은 varibales.js 파일을 생성했을 때 어떤 방식으로 적용할 수 있는지 예시와 함께 확인해 보도록 하겠습니다.
// variables.js
import { css } from 'styled-components';
const variables = {
flex: (direction = 'row', justify = 'center', align = 'center') =>`
display: flex;
flex-direction: ${direction};
justify-content: ${justify};
align-items: ${align};
`,
absoluteCenter: css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`,
};
export default variables;
ThemeProvider에서 prop으로 합쳐서 사용
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/GlobalStyle';
import theme from './styles/theme';
import variables from './styles/variables'
import Router from './Router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider theme={{ style: theme, variables }}>
<GlobalStyle />
<Router />
</ThemeProvider>
);
사용할 때는 다음과 같이 적용할 수 있습니다.
// App.js
const App = () => {
return (
<Container>
<Button>첫번째 버튼</Button>
<Button>두번째 버튼</Button>
</Container>
);
};
const Container = styled.div`
${(props) => props.theme.variables.flexSet()}
`; // a
const Button = styled.button`
background-color: ${(props) => props.theme.style.lightGrey};
`;
export default App;
각 컴포넌트에서 variables.js 파일을 import해서 사용
// App.js
import styled from 'styled-components';
import variables from './styles/variables';
const App = () => {
return (
<Wrapper>
<Button primary="pink">Pink Button</Button>
<Button primary="Yellow">Yellow Button</Button>
</Wrapper>
);
};
const Wrapper = styled.div`
${variables.flex()}
`;
const Button = styled.button`
margin: 20px;
padding: 20px;
border: none;
background-color: ${(props) => props.primary};
font-size: 20px;
`;
export default App;
전역에서 사용하길 원하면 GlobalStyle.js의 body안에 font-family를 웹 폰트 명으로 지정해주어야 합니다. 그리고 웹 폰트를 사용할 것인지, 폰트 파일을 사용할 것인지에 대해서 설정방법이 달라집니다.
// GlobalStyle.js
import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';
const GlobalStyle = createGlobalStyle`
${reset}
body{
font-family: 'Do Hyeon', sans-serif;
}
`;
export default GlobalStyle;
웹 폰트 사용할 때는 웹 폰트 사이트에서 제공하는 embedding(임베드) 코드를 복사하여 html 파일에 코드를 추가해야 합니다. index.html의 head안에 link 태그를 이용해서 글로벌 폰트로 지정해서 사용할 수 있습니다.
// index.html
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Do+Hyeon&display=swap"
rel="stylesheet"
/>
</head>
src
└── styles
├── fonts
│ └── DoHyeon-Regular.ttf
├── GlobalFont.js
├── GlobalStyle.js
└── theme.js
// src/styles/GlobalFont.js
import { createGlobalStyle } from 'styled-components';
import DoHyeon from './fonts/DoHyeon-Regular.ttf';
const GlobalFont = createGlobalStyle`
@font-face {
font-family: 'Do Hyeon';
src: url(${DoHyeon}) format('woff');
}
`;
export default GlobalFont;
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/GlobalStyle';
import GlobalFont from './styles/GlobalFont';
import color from './styles/theme';
import Router from './Router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ThemeProvider theme={theme}>
<GlobalStyle />
<GlobalFont />
<Router />
</ThemeProvider>
);