● Sass에서 지원하는 두 가지 확장자 .scss .sass
3.3 CSS Module이 아닌 파일에서 CSS Module 사용하기
: 스타일을 적용해 컴포넌트를 꾸미는 것
리액트에서 자주 사용하는 컴포넌트 스타일링 방식
실습 프로젝트 준비 ▶ 일반 CSS 사용 ▶ Sass 사용 ▶ CSS Module 사용 ▶ styled-components 사용
터미널에서 다음 명령을 수행한다.
$ yarn create react-app styling-react
$ cd styling-react
$ yarn start
기본적으로 프로젝트는 일반 CSS 방식으로 만들어져 있다.
새로운 스타일링 시스템을 적용하기 불편한 소규모 프로젝트라면 기본 방식을 사용해도 충분하다.
아래는 프로젝트 생성 시 기본으로 만들어진 App.js 파일과 App.css 파일이다.
// App.js
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
/* App.css */
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
CSS 작성 시 가장 중요한 점 CSS 클래스를 중복되지 않게 만들기!
CSS Selector는 CSS 스타일 작성 시 특정 태그(HTML 태그, ID를 가진 태그, 클래스에 포함된 태그, 그외 특별한 관계에 있는 태그)를 가리킬 때 사용된다.
CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용하려면 .(바깥쪽 태그의 클래스) .(안쪽 태그의 클래스) 형식으로 작성한다.
예) .App 안의 .logo에 스타일을 적용할 때
.App .logo {
animation: App-logo-spin infinite 20x linear;
height: 40vmin;
}
CSS Selector를 이용하는 방식으로 App.css를 수정해보자.
/* App.css */
.App {
text-align: center;
}
/* .App 안의 .logo */
.App .logo {
height: 40vmin;
pointer-events: none;
}
/* .App 안의 header
header 클래스가 아닌 header 태그 자체에
스타일을 적용하기 때문에 .이 생략됨 */
.App header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
/* .App 안의 a 태그 */
.App a {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
이에 맞춰 컴포넌트의 JSX 부분도 수정한다.
수정된 부분
<header> className 삭제
<img className="logo"> className 변경
// App.js
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header>
<img src={logo} className="logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
/* .sass 확장자 문법 */
$font-stack: Helvetica, sans-serif
&primary-color: #333
body
font: 100% $font-stact
color: $primary-color
아래의 .scss 문법이 더 자주 사용된다. 중괄호({})와 세미콜론(;)을 사용한다.
/* .scss 확장자 문법 */
$font-stack: Helvetica, sans-serif;
&primary-color: #333;
body {
font: 100% $font-stact;
color: $primary-color;
}
컴포넌트에서 Sass를 사용하려면 Sass를 CSS로 변환해주는 Sass 라이브러리를 설치해야 한다.
프로젝트 디렉터리에서 다음 터미널 명령어를 실행한다.
$ yarn add sass
설치가 완료되면 src 디렉터리에 SassComponent.scss 파일을 작성한다.
// SassComponent.scss
// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// 믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
.SassComponent {
display: flex;
.box {
background: red; // 일반 CSS 에선 .SassComponent .box 와 마찬가지
cursor: pointer;
transition: all 0.3s ease-in;
&.red {
// .red 클래스가 .box 와 함께 사용 됐을 때
background: $red;
@include square(1);
}
&.orange {
background: $orange;
@include square(2);
}
&.yellow {
background: $yellow;
@include square(3);
}
&.green {
background: $green;
@include square(4);
}
&.blue {
background: $blue;
@include square(5);
}
&.indigo {
background: $indigo;
@include square(6);
}
&.violet {
background: $violet;
@include square(7);
}
&:hover {
// .box 에 마우스 올렸을 때
background: black;
}
}
}
src 폴더에 이 Sass 스타일시트를 사용하는 SassComponent.js 컴포넌트 파일도 작성한다.
// SassComponent.js
import React from 'react';
import './SassComponent.scss';
const SassComponent = () => {
return (
<div className="SassComponent">
<div className="box red" />
<div className="box orange" />
<div className="box yellow" />
<div className="box green" />
<div className="box blue" />
<div className="box indigo" />
<div className="box violet" />
</div>
);
};
export default SassComponent;
이 컴포넌트를 App 컴포넌트에서 보여주도록 한다.
// App.js
import {Component} from 'react';
import SassComponent from './SassComponent';
class App extends Component {
render() {
return (
<div>
<SassComponent />
</div>
);
}
}
export default App;
작업한 뒤에는 개발 서버를 재시작해야 Sass가 성공적으로 적용된다.
(개발 서버가 구동 중이라면 터미널 창에서 Ctrl + C로 빠져나오고, 다시 yarn start 명령어를 입력한다.)
서버 재시작시 다음과 같은 페이지가 나타난다.
여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 별도의 파일로 분리하면 필요한 곳에서 쉽게 불러와 사용할 수 있다.
* 믹스인: 함수처럼 재사용할 수 있는 스타일 코드 블록
src 디렉터리에 styles 디렉터리를 생성하고, utils.scss 파일을 작성한다.
기존 SassComponent.scss에 작성했던 변수와 믹스인을 잘라내서 이동시킨다.
// src/styles/utils.scss
// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// 믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
utils.scss 파일에서 선언한 변수와 믹스인을 SassComponent.scss에서 사용해보자.
scss 파일에서 다른 scss 파일을 불러올 때는 @import 구문을 사용한다.
// SassComponent.scss
@import "./styles/utils";
(...)
utils.scss 파일을 분리하기 전과 같은 결과가 나타난다.
위처럼 다른 파일을 임포트할 때, 프로젝트에 디렉터리가 많아져 구조가 깊어졌다면 상대경로로 임포트하기 불편해진다.
웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하면 styles 디렉터리를 기준으로 한 절대 경로를 사용할 수 있다.
create-react-app으로 만든 프로젝트는 구조의 복잡도를 낮추기 위해 세부 설정이 모두 숨겨져 있어,
이를 커스터마이징하려면 프로젝트 디렉터리에서 yarn eject 명령어로 꺼내주어야 한다.
create-react-app에서는 기본적으로 Git 설정이 되어있는데,
yarn eject는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않으니 먼저 커밋해주어야 한다.
커밋을 진행하려면 VS Code 좌측의 Git UI를 사용하거나, 터미널에서 다음 명령을 사용한다.
$ git add .
$ git commit -m'Commit before yarn eject'
커밋을 마쳤다면 다음 작업을 실행한다.
$ yarn eject
$ react-scripts eject
? Are you sure you want to eject? This action is permanent. (y/N) y
위 작업을 마치면 프로젝트 디렉터리에 config라는 디렉터리가 생성된다.
디렉토리 내 webpack.config.js를 열어보자.
sassRegex 키워드를 검색하면 두 번째 탐색 결과에서 다음과 같은 코드가 나타날 것이다.
// config/webpack.config.js
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
sideEffects: true,
},
use: 에 있는 'sass-loader' 부분을 지우고, 뒷부분에 concat을 통해 커스터마이징된 sass-loader 설정을 추가한다.
// config/webpack.config.js
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
}).concat({
loader: require.resolve("sass-loader"),
options: {
sassOptions: {
includePaths: [paths.appSrc + "/styles"],
},
},
}),
sideEffects: true,
},
SassComponent.scss 파일에서 import 구문을 다음과 같이 수정해 본다. 똑같이 적용되는 것을 확인할 수 있다.
@import 'utils.scss';
sass-loader의 additionalData 옵션을 설정하면 Sass 파일을 불러올 때마다 코드의 맨 윗부분에 특정 코드를 포함시켜 준다.
이것을 이용하면 import 구문을 쓰지 않고도 utils.scss 파일을 불러오는 행위를 자동화할 수 있다!
webpack.config.js를 열어서 sass-loader 옵션의 additionalData 필드를 다음과 같이 설정해보자. (아래에서 5번째줄)
// config/webpack.config.js
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
}).concat({
loader: require.resolve("sass-loader"),
options: {
sassOptions: {
includePaths: [paths.appSrc + "/styles"],
},
additionalData: "@import 'utils';",
},
}),
sideEffects: true,
},
① 상대 경로 사용 비추천
스타일 파일이 깊숙한 디렉터리에 위치한 경우 ../를 매우 많이 적어야 해 번거롭다.
@import '../../../node_modules/library/styles';
② 물결 문자(~) 사용 추천!
자동으로 node_modules 폴더에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있다.
@import '~library/styles';
라이브러리를 다음 yarn 명령어를 사용해 설치한다.
$ yarn add open-color include-media
설치한 라이브러리를 utils.scss에서 불러온다.
※ node_modules 내부 라이브러리의 scss 파일의 경로는 라이브러리의 공식 매뉴얼에서 알려주지 않는 경우도 많으니, 직접 경로로 들어가서 확인하길 바람.
// utils.scss
@import "~include-media/dist/include-media";
@import "~open-color/open-color";
라이브러리를 사용하기 위해 SassComponent.scss 파일을 수정한다.
// SassComponent.scss
.SassComponent {
display: flex;
background: $oc-gray-2;
@include media("<768px") {
background: $oc-gray-9;
}
(...)
}
이 코드는 SassComponent의 배경색을 open-colors 팔레트 라이브러리에서 불러온 후 설정하고, 화면 가로 크기가 768px 미만이 되면 배경색을 어둡게 바꿔 준다. 코드를 저장하고 나면 다음과 같은 결과물이 나타난다.
CSS를 불러와서 사용할 때 클래스 이름을 고유한 형태로 만들어(자동으로 [파일 이름]_[클래스 이름]_[해시값]으로 변환),
컴포넌트 스타일 클래스 이름이 중첩되지 않도록 해 주는 기술이다.
구버전(v1)의 create-react-app에서는 웹팩에서 css-loader 설정을 별도로 해야 했지만,
v2 버전 이상부터는 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.
CSSModule.module.css 파일을 src 디렉터리에 생성해 다음과 같이 작성해보자.
/* CSSModule.module.css */
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
&.inverted {
// inverted 가 .wrapper 와 함께 사용 됐을 때만 적용
color: black;
background: white;
border: 1px solid black;
}
}
/* 글로벌 CSS 를 작성하고 싶다면 */
:global {
// :global {} 로 감싸기
.something {
font-weight: 800;
color: aqua;
}
// 여기에 다른 클래스를 만들 수도 있겠죠?
}
CSS Module을 사용하면 클래스 이름을 지을 때 중복 여부를 고민하지 않아도 된다. 흔한 단어로 지어도 상관없다.
해당 클래스는 .module.css로 작성한 스타일을 직접 불러온 컴포넌트 내부에서만 작동하기 때문이다.
특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우에는 :global을 앞에 입력하여 전역 CSS임을 명시할 수 있다.
위의 CSS Module을 사용하는 리액트 컴포넌트도 작성해보자.
// CSSModule.js
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
CSS Module이 적용된 스타일 파일을 불러오면 객체를 하나 전달받는데,
CSS Module에서 사용한 클래스 이름, 해당 이름을 고유화한 값이 키-값 형태로 들어 있다.
위 코드에서 console.log(styles)를 하면 다음과 같이 출력된다.
{ wrapper: "CSSModule_wrapper__15bdQ" }
이 고유한 클래스 이름을 사용하려면 ▶ JSX 엘리먼트에 className={styles.[클래스이름]} 형태로 전달해주면 된다.
전역적으로 선언한 :global 클래스의 경우 ▶ 전형적인 방식대로 문자열을 넣는다.
CSSMocule 관련 컴포넌트와 스타일을 App 컴포넌트에서 렌더링해보자.
// App.js
import {Component} from 'react';
import CSSModule from './CSSModule';
class App extends Component {
render() {
return (
<div>
<CSSModule />
</div>
);
}
}
export default App;
CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는 다음과 같이 코드를 작성한다.
/* CSSModule.module.css */
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
.inverted {
color: black;
background: white;
border: 1px solid balck;
}
/* 글로벌 CSS 를 작성하고 싶다면 */
:global .something {
font-weight: 800;
color: aqua;
}
// CSSModule.js
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={`${styles.wrapper} %{styles.inverted}`}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
위 코드에서는 ES6 문법 템플릿 리터럴을 사용하여 문자열을 합해주었다.
이 문법을 사용하면 문자열 안에 자바스크립트 레퍼런스를 쉽게 넣어줄 수 있다.
const name = '리액트';
// const message = '제 이름은 ' + name + '입니다.';
const message = `제 이름은 ${name}입니다.`;
` 문자는 백틱(Backtick)이라고 부른다. (키보드의 숫자 키 [1] 왼쪽에 위치)
템플릿 리터럴 문법 대신 다음과 같이 작성할 수도 있다.
className={[styles.wrapper, styles.inverted].join(' ')}
: CSS를 조건부로 설정하거나, CSS Module 사용 시 여러 클래스를 적용할 때 편리하게 사용할 수 있는 라이브러리
$ yarn add classnames 명령으로 설치한다.
import classNames from 'classnames';
classNames('one', 'two'); // = 'one two'
classNames('one', { two: true }); // = 'one two'
classNames('one', { two: false }); // = 'one'
classNames('one', ['two', 'three']); // = 'one two three'
const myClass = 'hello';
classNames('one', myClass, {myCondition: true}); // = 'one hello myCondition'
여러 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있어 컴포넌트에서 조건부로 클래스를 설정할 때 유용하다.
예) props 값에 따라 다른 스타일을 적용하기가 쉬워진다.
// 예시 코드
const MyComponent = ({ highlighted, theme }) => (
<div className={classNames('MyComponent', { highlighted }, theme)}>Hello</div>
);
이 경우 highlighted 값이 true이면 highlighted 클래스가 적용되고, false이면 적용되지 않는다.
추가로 theme로 전달받는 문자열은 내용 그대로 클래스에 적용된다.
이런 라이브러리의 도움을 받지 않는다면 다음과 같은 복잡한 형식으로 처리해야 할 것이다.
const MyComponent = ({highlighted, theme }) => (
<div className={`MyComponent ${theme} ${highlighted ? 'highlighted' : ''}`}>
hello
</div>
);
classnames에 내장된 bind 함수를 사용하면 클래스를 넣어줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없다.
변수를 정의해 미리 styles에서 클래스를 받아오면 cx('클래스 이름', '클래스 이름2') 형태로 간단히 사용할 수 있다.
예) 앞서 만든 CSSModule 컴포넌트에 classnames의 bind 함수 적용하기
// CSSModule.js
import classNames from 'classnames/bind';
import styles from './CSSModule.module.css';
const cx = classNames.bind(styles); // 미리 styles에서 클래스를 받아오도록 설정
const CSSModule = () => {
return (
<div className={cx('wrapper', 'inverted')}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해주면 CSS Module로 사용할 수 있다.
CSSModule.module.css 파일의 이름을 CSSModule.Module.scss로 변경하고 코드를 수정해보자.
/* CSSModule.module.xcss */
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
&.inverted {
color: black;
background: white;
border: 1px solid black;
}
/* 글로벌 CSS 를 작성하고 싶다면 */
:global {
font-weight: 800;
color: aqua;
}
CSSModule.js 파일의 상단에서도 .css 파일 대신 .scss 파일을 불러온다.
import styles from './CSSModule.module.scss';
CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼
CSS Module이 아닌 일반 .css/.scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있다.
:local .wrapper {
/* 스타일 코드 작성 */
}
:local {
.wrapper {
/* 스타일 코드 작성 */
}
}
이와 관련된 라이브러리의 종류는 https://github.com/MicheleBertoli/css-in-js 에서 확인할 수 있다.
그중 가장 많은 선호를 얻고 있는 라이브러리인 styled-components을 알아보자.
* styled-components를 대체할 수 있는 라이브러리로는 현재 emotion이 대표적이며, 둘의 작동 방식은 비슷하다.
이 라이브러리는 다음의 터미널 명령어로 설치할 수 있다.
$ yarn add styled-components
src 디렉터리에 StyledComponent.js 파일을 생성한 뒤 다음 예제 컴포넌트를 만들어보자.
/* StyledComponent.js */
import styled, { css } from 'styled-components';
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`;
const Button = styled.button`
background: white;
color: black;
border-radius: 4px;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 1rem;
font-weight: 600;
/* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
&:hover {
background: rgba(255, 255, 255, 0.9);
}
/* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
${props =>
props.inverted &&
css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
& + button {
margin-left: 1rem;
}
`;
const StyledComponent = () => (
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
);
export default StyledComponent;
// App.js
import {Component} from 'react';
import StyledComponent from './StyledComponent';
class App extends Component {
render() {
return (
<div>
<StyledComponent />
</div>
);
}
}
export default App;
실행하면 다음과 같은 화면이 나타난다.
컴포넌트 내부에 작성한 스타일을 문자열로 취급되어 코드 신택스 하이라이팅(문법에 따라 에디터 폰트 색상을 입히는 작업)이 제대로 이루어지지 않을 수 있다. VS Code의 마켓플레이스에서 vscode-styled-components를 검색하여 설치하면 색상이 정상적으로 입혀진다.
앞서 작성한 코드에서 `(백틱 문자)를 사용하여 만든 문자열에 스타일 정보를 넣어주었다.
이 문법을 Tagged 템플릿 리터럴이라고 한다.
CSS Module에서 다룬 일반 템플릿 리터럴과 달리, 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다.
일반 템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어버린다.
객체는 "[object Object]"로 변환되고 함수는 함수 내용이 그대로 문자열화되어 나타난다.
`hello ${{foo: 'bar' }} ${() => 'world'}!`
// 결과: "hello [object Object] () => 'world'!"
작성한 함수 뒤에 템플릿 리터럴을 넣어 준다면, 템플릿 안에 넣은 값을 온전히 추출할 수 있다.
function tagged(...args) {
console.log(args);
}
tagged`hello ${{foo: 'bar' }} ${() => 'world'}!`
위 코드를 (크롬 브라우저의 개발자 콘솔 등의) 자바스크립트 콘솔에 붙여 넣으면 다음과 같은 결과가 나타난다.
이와 같은 방법으로 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있다.
import styled from 'styled-components';
const MyComponent = styled.div`
font-size: 2rem;
`;
사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링하려면 다음과 같은 형태로 구현할 수 있다.
① 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
background: gray;
`
② 컴포넌트 형식의 값을 styled 함수의 인자로 전달 * Link는 후에 리액트 라우터를 다룰 때 사용하는 컴포넌트
const StyledLink = styled(Link)`
color: blue;
`
컴포넌트(Sample)를 styled의 파라미터에 넣는 경우,
해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정하는 작업이 내부적으로 되어 있어야 한다.
const Sample = ({ className }) => {
return <div className={classname}>Sample</div>;
};
const StyledSample = styled(Sample)`
font-size: 2rem;
`;
styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있다.
이전에 작성했던 Box 컴포넌트를 보자. (코드 3번째 줄)
/* StyledComponents.js - Box 컴포넌트 */
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`;
이렇게 만들어진 컴포넌트(Box)는 JSX에서 사용될 때 다음과 같이 props(color)의 값("black")을 사용할 수 있다.
<Box color="black">(...)</Box>
일반 CSS 클래스를 사용한 조건부 스타일링 ▶ className을 사용한다.
styled-components에서 조건부 스타일링 ▶ 간단하게 props로 처리한다.
Button 컴포넌트를 살펴보자.
/* StyledComponents.js - Button 컴포넌트 */
import styled, { css } from 'styled-components';
/* 단순 변수 형태가 아니라
여러 줄의 스타일 구문을 조건부로 설정해야 하는 경우
css를 임포트해야 함 */
const Button = styled.button`
background: white;
color: black;
border-radius: 4px;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 1rem;
font-weight: 600;
/* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
&:hover {
background: rgba(255, 255, 255, 0.9);
}
/* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
${props =>
props.inverted &&
css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
& + button {
margin-left: 1rem;
}
`;
이렇게 만든 컴포넌트는 다음과 같이 props에 따라 서로 다른 스타일을 조건부로 적용할 수 있다.
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
① props 값에 따라 서로 다른 스타일을 적용하려는 경우
반드시 styled-components에서 임포트한 CSS로 감싸고, Tagged 템플릿 리터럴을 사용해야 한다.
② props를 참조하지 않는 경우
굳이 CSS를 사용하지 않고 바로 문자열을 넣어도 작동하기는 한다.
${props =>
props.inverted &&
`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
단점
styled-components를 사용한 반응형 디자인을 살펴보자.
브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 같이 media 쿼리를 사용하면 된다.
Box 컴포넌트를 다음과 같이 수정한다.
/* StyledComponents.js - Box 컴포넌트 */
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
/* 기본적으로는 가로 크기 1024px에 가운데 정렬을 하고
가로 크기가 작아짐에 따라 크기를 줄이고
768px 미만이 되면 꽉 채웁니다. */
width: 1024px;
margin: 0 auto;
@media (max-width: 1024px) {
width: 768px;
}
@media (max-width: 768px) {
width: 100%;
}
`;
이런 작업을 여러 컴포넌트에서 반복해야 할 때는 이 작업을 함수화하여 간편하게 사용할 수 있다.
styled-components 매뉴얼에서 제공하는 유틸 함수를 따라 사용해보자.
import styled, { css } from 'styled-components';
const sizes = {
desktop: 1024,
tablet: 768
};
// 위에 있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어 줍니다.
// 참고: https://www.styled-components.com/docs/advanced#media-templates
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
@media (max-width: ${sizes[label] / 16}em) {
${css(...args)};
}
`;
return acc;
}, {});
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
width: 1024px;
margin: 0 auto;
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`}
`;
media를 한번 선언해두면 이를 사용하는 스타일 코드가 간단해진다.
현재는 media를 StyledCompoent.js에 만들어두었지만, 아예 다른 파일로 분리해 모듈화한 뒤 필요한 곳에 불러와 사용하면 훨씬 실용적일 것이다.
1. CSS를 작성할 때 가장 중요한 점은 (클래스 이름)을 중복되지 않게 만드는 것이다.
Sass
2. Sass가 지원하는 2가지 확장자 중 (.sass)보다 (.scss) 문법이 더 자주 사용되며, 이 문법에서는 (중괄호)와 (세미콜론)을 사용한다.
3. (믹스인)이란 함수처럼 재사용할 수 있는 스타일 코드 블록을 말한다.
4. scss 파일에서 다른 scss 파일을 불러올 떄는 (@import) 구문을 사용한다.
5. sass-loader의 설정을 커스터마이징하면 다른 파일을 임포트할 때 (styles) 디렉터리 기준 절대 경로를 사용하거나, Sass 파일을 불러올 때마다 특정 코드를 포함시키도록 할 수 있다. 이것은 config 디렉터리의 (webpack.config.js) 파일에서 설정할 수 있다.
6. 파일을 임포트할 때 (물결 문자(~))를 사용하면 자동으로 node_modules 폴더에서 라이브러리를 탐지하여 스타일을 불러온다.
CSS Module
7. (.module.css) 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.
8. [파일 이름]_[클래스 이름]_[해시 값]으로 변환된 고유한 클래스 이름을 사용하려면 JSX 엘리먼트에 (className={styles.[클래스이름]}) 형태로 전달하면 된다. (:global) 키워드를 사용해 전역적으로 선언한 클래스의 경우 그냥 문자열을 넣는다.
9. ES6 문법 템플릿 리터럴: 문자열을 감싸는 따옴표로 (`(백틱 문자))를 사용한다. 사용 형태: `${변수}`
10. (classnames) 라이브러리를 사용하면 여러 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있어 컴포넌트에서 (조건부)로 클래스를 설정할 때 유용하다. 이 라이브러리에 내장된 (bind 함수)를 사용하면 styles.클래스이름 형태를 사용할 필요 없이 간단하게 지정할 수 있다.
11. Sass를 사용할 때도 파일 이름 뒤에 (.module.scss) 확장자를 사용하면 CSS Module로 사용할 수 있다.
12. CSS Module이 아닌 일반 .css/.scss 파일에서도 (:local)을 사용하여 CSS Module을 사용할 수 있다.
styled-components
13. 일반 className을 사용하는 CSS/Sass와 비교했을 때, (props)에 전달되는 값을 쉽게 스타일에 적용할 수 있다.
14. Tagged 템플릿 리터럴은 일반 템플릿 리터럴과 달리, 자바스크립트 (객체)나 (함수)를 전달할 때 온전히 추출할 수 있다.
15. (styled 함수)를 임포트하고, (styled.태그명) 뒤에 (Tagged 템플릿) 문법을 통해 스타일을 넣어주어 엘리먼트를 만들 수 있다.
16. (props)에 따라 조건부 스타일링을 할 수 있다.
1. 다음 ES6 문법 템플릿 리터럴로 작성된 코드를 classnames 라이브러리의 bind 함수를 이용해 간단히 작성해보자.
// CSSModule.js
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={`${styles.wrapper} %{styles.inverted}`}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
2. 다음 컴포넌트에서 color(글자 색상) 값을 설정할 때 props를 조회하여 props.fontColor의 값을 사용하게 하고,
fontColor 값이 주어지지 않았을 때에는 red를 기본 색상으로 사용하도록 설정하라.
/* StyledComponents.js - Box 컴포넌트 */
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
color: /* 이 부분을 작성 */
padding: 1rem;
display: flex;
`;
Corner React Starter #2
Editor 유즈
<리액트를 다루는 기술> 11장: 컴포넌트 성능 최적화 (0) | 2022.01.17 |
---|---|
<리액트를 다루는 기술> 10장: 일정 관리 웹 애플리케이션 만들기 (0) | 2022.01.10 |
<리액트를 다루는 기술> 8장: Hooks (0) | 2021.12.27 |
<리액트를 다루는 기술> 7장: 컴포넌트의 라이프사이클 메서드 (0) | 2021.12.27 |
<리액트를 다루는 기술> 6장: 컴포넌트 반복 (0) | 2021.11.29 |