📗목차
일반 CSS:
컴포넌트를 스타일링하는 가장 기본적인 방식이다.
Sass:
자주 사용되는 CSS 전처리기(pre-processor) 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있도록 해준다.
CSS Module:
스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션이다.
styled-components:
스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 해준다.
CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것이다. CSS 클래스가 중복되는 것을 방지하는 여러 가지 방식이 있는데, 그 중 하나는 이름을 자을 때 특별한 규칙을 사용하여 짓는 것이고, 또 다른 하나는 CSS Selector를 활용하는 것이다.
리액트 프로젝트를 생성하면 자동 생성된 App.css를 읽어 보면 클래스 이름이 컴포넌트-이름-클래스 형태로 지어져 있다(예: App-header). 클래스 이름에 컴포넌트를 이름을 포함시킴으로써 다른 컴포넌트에서 실수로 중복되는 클래스를 만들어 사용하는 것을 방지할 수 있다. 비슷한 방식으로 BEM 네이밍이라는 방식도 있다. BEM 네이밍은 CSS 방법론 중 하나로, 이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식이다. 예를 들어 .card_title_primary처럼 말이다.
CSS Selector를 사용하면 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있다. 예를 들어 .App 안에 들어 있는 .logo에 스타일을 적용하고 싶다면 다음과 같이 작성하면 된다.
.App .logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
Sass(Syntactically Awesome Style Sheets)는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여 유지 보수를 더욱 쉽게 해준다.
create-react-app 구버전에서는 Sass를 사용하려면 추가 작업이 필요했는데, v2 버전부터는 별도의 추가 설정 없이 바로 사용할 수 있다.
Sass에서는 두 가지 확장자 .scss와 .sass를 지원한다. Sass가 처음 나왔을 때는 .sass 확장자만 지원되었으나 나중에 개발자들의 요청에 의해 .scss 확장자도 지원하게 되었다.
.scss의 문법과 .sass의 문법은 꽤나 다르다.
.sass
$font-stack: Helvetica, sans-serif $primary-color: #333 body font: 100% $font-stack color: $primary-color
.scss
$font-stack: Helvetica, sans-serif; $primary-color: #333; body { font: 100% $font-stack; color: $primary-color; }
주요 차이점을 살펴보면, sass 확장자는 중괄호({})와 세미콜론(;)을 사용하지 않는다.
반면 .scss 확장자는 기존 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않다. 보통 .scss 문법이 더 자주 사용되므로 이 책에서는 .scss 확장자를 사용하여 스타일을 작성해보겠다.
새 컴포넌트를 만들어서 Sass를 한번 사용해보자. 우선 node-sass라는 라이브러리를 설치해줘야 한다. 이 라이브러리는 Sass를 CSS로 변환해준다.
다음 명령어를 입력하자. npm i node-sass
하지만, 설치 에러가 떴다. node.js 버전이 node-sass 버전이랑 호환이 안 돼서 생기는 문제라는대.
node-sass는 nodejs 버전에 영향을 받습니다.
그리고 node-sass 대신 sass 패키지 사용을 권장합니다.
sass는 node-sass를 흡수한 최신 버전의 패키지입니다.
npm i -D sass 로 설치하시면 됩니다.
node-sass 설치 오류 참조
구글링 결과 다음 명령어를 입력하면 되었다.
npm i -D sass
설치가 완료되면 다음과 같이 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 { // 일반 CSS에서는 .SassComponent .box와 마찬가지
background: red;
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;
}
}
}
그리고 이 Sass 스타일시트를 사용하는 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 컴포넌트에서 보여주자.
import React, { Component } from 'react';
import SassComponent from './SassComponent';
class App extends Component {
render() {
return (
<div><SassComponent /></div>
);
}
}
export default App;
작업한 뒤에는 개발 서버를 재시작시켜야 Sass가 성공적으로 적용된다.
개발 서버가 구동 중인 터미널 창에서 Ctrl+C를 누르고, 다시 npm 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; }
다른 scss 파일을 불러올 때는 @import 구문을 사용한다.
SassComponent.scss
@import './styles/utils'; .SassComponent { display: flex; .box { // 일반 CSS에서는 .SassComponent .box와 마찬가지 background: red; cursor: pointer; transition: all 0.3s ease-in; (...) } }
utils.scss 파일을 분리하기 전의 결과와 같은 결과가 나타나는지 확인해보자.
이 작업은 Sass를 사용할 때 반드시 해야 하는 것은 아니지만, 해두면 유용하다. 예를 들어 방금 SassComponent에서 utils를 불러올 때 @import './styles/utils';
형태로 불러왔는데, 만약 프로젝트에 디렉터리를 많이 만들어서 구조가 깊어졌다면 해당 파일에서 다음과 같이 상위 폴더로 한참 거슬러 올라가야 한다는 단점이 있다.
@import '.../.../.../styles/utils';
이 문제점은 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하여 해결할 수 있다. create-react-app으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위해 세부 설정이 모두 숨겨져 있다. 이를 커스터마이징하려면 프로젝트 디렉터리에서 npm run eject
명령어를 통해 세부 설정을 밖으로 꺼내줘야 한다.
create-react-app에서는 기본적으로 Git 설정이 되어 있는데, npm run eject
는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않으니, 먼저 커밋해야 한다.
VS Code 좌측에 있는 Git UI를 사용하거나,
다음 명령어를 통해 지금까지 한 작업을 커밋하자.
git add .
git commit -m "Commit before npm run eject"
그리고 나서 npm run eject
명령어를 실행한다.
이제 프로젝트 디렉터리에 config라는 디렉터리가 생성되었을 것이다. 그 디렉터리 안에 들어 있는 webpack.config.js를 열어보자.
그 파일에서 "sassRegex"라는 키워드를 찾아 보면(Ctrl + F) 두 번째 탐색 결과에서 아래와 같은 코드가 나타날 것이다.
여기서 use:에 있는 'sass-loader' 부분을 지우고, 뒷부분에 concat을 통해 커스터마이징된 sass-loader 설정을 넣어주자.
커스터마이징한 sass-loader
{ test: sassRegex, exclude: sassModuleRegex, use: getStyleLoaders({ importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap }).concat({ loader: require.resolve('sass-loader'), options: { includePaths: [paths.appSrc + '/styles'], sourceMap: isEnvProduction && shouldUseSourceMap, } } }), sideEffects: true, },
리액트 버전 업데이트로 인해 위의 코드는 이러한 에러가 발생한다.
'options has an unknown property 'includePaths'. These properties are valid'
이 문제를 해결하기 위해 아래의 링크를 통해 참고하자.
커스터마이징 에러 글 참조
"options안에 또 다른 옵션들을 설정해 주어야 하기 때문에, 다짜고짜 sass 옵션을 써버리면 안되고, sassOptions 안에 정의를 해주어야 한다."
커스터마이징한 sass-loader 코드에서 options 안에 코드만 수정해준다.
options: { sassOptions: { includePaths: [paths.appSrc + '/styles'], sourceMap: isEnvProduction && shouldUseSourceMap, } }
설정 파일을 저장한 후, 서버를 껐다가 재시작하자.
이제 utils.scss 파일을 불러올 때 현재 수정하고 있는 scss 파일 경로가 어디에 위치하더라도 앞부분에 상대 경로를 입력할 필요 없이 styles 디렉터리 기준 절대 경로를 사용하여 불러올 수 있다.
SassComponent.scss 파일에서 import 구문을 다음과 같이 수정해보자. 그리고 똑같이 적용되는지 확인해보아라.
@import './utils';
이제부터 utils.scss를 사용하는 컴포넌트가 있다면 위 한 줄만 넣어주면 된다.
하지만 새 파일을 생성할 때마다 utils.scss를 매번 이렇게 포함시키는 것도 귀찮을 수 있다. 그럴 때는 sass-loader의 data 옵션을 설정하면 된다.
data 옵션을 설정하면 Sass 파일을 불러올 때마다 코드의 맨 윗부분에 특정 코드를 포함시켜 준다.
webpack.config.js를 열어서 조금 전 수정했던 sass-loader의 옵션에 있는 data 필드를 다음과 같이 설정해보자.
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders({
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap
}).concat({
loader: require.resolve('sass-loader'),
options: {
sassOptions: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap,
data: `@import 'utils';`
}
}
}),
sideEffects: true,
},
이렇게 작성하면 모든 scss 파일에서 utils.scss를 자동으로 불러오므로, Sass에서 맨 윗줄에 있는 import 구문을 지워도 정상적으로 작동할 것이다.
라고 했는데 또 리액트 버전이 업데이트가 많이 된 것인지... sassOptions 객체에 data 키가 다른 걸로 바뀐 거 같다.
sass-loader 설정 글 참고
options: {
additionalData: `@import 'utils';`,
sassOptions: {
includePaths: [paths.appSrc + "/styles"],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
위의 링크에서 보았던 코드를 그대로 수정하였는데도...Sass에서 맨 윗줄에 있는 import 구문을 지우면 에러가 발생한다.
주석 처리를 한 부분 때문에 utils.scss에서 $red를 정의한 것이 불러와지지 않아서이다.
구글링 해봐도 chatGPT로 검색을 해봐도 어디를 수정해야 될지 모르겠다.
이번 건 킵해둬야 될 거 같다.
Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점이다. npm을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법에 대해 알아보자.
다음과 같이 상대 경로를 사용하여 node_modules까지 들어와서 불러오는 방법이다.
import '../../../node_modules/library/styles';
하지만 이런 구조는 스타일 파일이 깊숙한 디렉터리에 위치할 경우 ../를 매우 많이 적어야 하니 번거로울 것이다. 더 쉬운 방법은 바로 물결 문자(~)를 사용하는 방법이다.
import '~library/styles';
물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있다.
유용한 Sass 라이브러리 두 가지를 설치하고 사용해보겠다.
반응형 디자인을 쉽게 만들어 주는 include-media(https://include-media.com/)와 매우 편리한 색상 팔레트인 open-color(https://www.npmjs.com/package/open-color)를 npm 명령어를 사용해 설치해보자.
npm i include-media open-color
그 다음에는 utils.scss 파일을 열고 물결 표시를 사용하여 라이브러리를 불러오자. 다음 두 줄을 코드 상단에 넣어주면 된다.
utils.scss
@import '~include-media/dist/include-media'; @import '~open-color/open-color'; (...)
위에서 불러온 include-media와 open-color를 SassComponent.scss에서 사용해보자. 해당 스타일 파일을 아래와 같이 수정해보자.
SassComponent.scss
.SassComponent { display: flex; background: $oc-gray-2; @include media('<768px') { background: $oc-gray-9; } .box { (...) } }
이 코드는 SassComponent의 배경색을 open-colors 팔레트 라이브러리에서 불러온 후 설정하고, 화면 가로 크기가 768px 미만이 되면 배경색을 어둡게 바꿔 준다.
CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값,
즉 [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술이다.
CSS Module을 사용하기 위해 구버전(v1)의 create-react-app에서는 웹팩에서 css-loader 설정을 별도로 해줘야 했지만, v2 버전 이상부터는 따로 설정할 필요 없이 .module.css 확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.
CSSModule.module.css라는 파일을 src 디렉터리에 생성하여 다음과 같이 한번 작성해보자.
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
/* 글로벌 CSS를 작성하고 싶다면 */
:global .something {
font-weight: 800;
color: aqua;
}
CSS Module을 사용하면 클래스 이름을 지을 때 그 고유성에 대해 고민하지 않아도 된다. 해당 클래스는 방금 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동하기 때문이다.
만약 특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우라면 :global을 앞에 입력하여 글로벌 CSS임을 명시해 줄 수 있다.
위의 CSS Module을 사용하는 리액트 컴포넌트도 작성해보자.
CSSModule.js
import React from 'react'; 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__1SbdQ" }
우리가 지정한 클래스 이름 앞뒤로 파일 이름과 해시값이 붙어있는 것을 확인할 수 있다.
이 고유한 클래스 이름을 사용하려면 클래스를 적용하고 싶은 JSX 엘리먼트에
className={styles.[클래스 이름]} 형태로 전달해주면 된다.
:global을 사용하여 전역적으로 선언한 클래스의 경우 평상시 해왔던 것처럼 그냥 문자열을 넣어준다.
그 다음 App 컴포넌트에서 렌더링해보자.
import React, { 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 black; } /* 글로벌 CSS를 작성하고 싶다면 */ :global .something { font-weight: 800; color: aqua; }
CSSModule.js
import React from 'react'; import styles from './styles/CSSModule.module.css'; const CSSModule = () => { return ( <div className={`${styles.wrapper} ${styles.inverted}`}> 안녕하세요, 저는 <span className="something">CSS Module!</span> </div> ); }; export default CSSModule;
위 코드에서 ES6 문법 템플릿 리터럴을 사용하여 문자열을 합해주었다.
이 문법을 사용하면 문자열 안에 자바스크립트 레퍼런스를 쉽게 넣어 줄 수 있다.
만약 템플릿 리터럴 문법을 사용하고 싶지 않다면 다음과 같이 작성할 수도 있다.
className={[styles.wrapper, styles.inverted].join(' ')}
classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리이다.
또한 CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리하다.
우선 해당 라이브러리를 설치하자.
npm i classnames --save
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를 쓰는 것이 가독성이 훨씬 좋아 보인다.
덧붙여 CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 bind 함수를 사용하면 클래스를 넣어줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없다. 사전에 미리 styles에서 받아 온 후 사용하게끔 설정해 두고 cx('클래스 이름', '클래스 이름2') 형태로 사용할 수 있다.
다음 코드는 우리가 만든 CSS Module 컴포넌트에 classnames의 bind 함수를 적용한 예이다.
import React from 'react';
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>
);
};
CSS Module을 사용할 때 클래스를 여러 개 설정하거나, 또는 조건부로 클래스를 설정할 때 classnames의 bind를 사용하면 훨신 편리하게 작성할 수 있을 것이다.
Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해주면 CSS Module로 사용할 수 있다.
CSSModule.module.css 파일의 이름을 CSSModule.module.scss로 한번 변경해보자. 스타일 코드도 이에 따라 조금 수정해보겠다.
CSSModule.module.scss
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */ .wrapper { background: black; padding: 1rem; color: white; font-size: 2rem; &.inverted { color: black; background: white; border: 1px solid black; } } /* 글로벌 CSS를 작성하고 싶다면 */ :global { // :global {}로 감싸기 .something { 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 {
/* 스타일 */
}
}
컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식이다. 이 방식을 'CSS-in-JS'라고 부른다.
CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components를 알아보자.
styled-components를 대체할 수 있는 라이브러리로 현재 emotion이 대표적이다. 작동 방식도 styled-components와 비슷하다.
아래의 명령어를 입력하여 설치하도록 하자.
npm i styled-components
이 라이브러리를 통해 컴포넌트를 만들어 보겠다. styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.
StyledComponent.js
import React from 'react'; 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 컴포넌트에서 보이도록 하자.
import React, { Component } from 'react';
import StyledComponent from './StyledComponent';
class App extends Component {
render() {
return (
<div>
<StyledComponent />
</div>
);
}
}
export default App;
styled-components와 일반 classNames를 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것이다.
VS Code를 사용할 때 styled-components를 위해 컴포넌트 내부에 작성한 스타일이 그저 문자열로 간주되어 코드 신택스 하이라이팅(문법에 따라 에디터 폰트 색상을 입히는 작업)이 제대로 이루어지지 않는다.
VS Code의 마켓플레이스에서 vscode-styled-components를 검색하여 설치하면 색상이 정상적으로 입혀진다.
위에서 작성한 코드를 확인해보면, 스타일을 작성할 때 `을 사용하여 만든 문자열에 스타일 정보를 넣어 주었다. 이것을 Tagged 템플릿 리터럴이라고 부른다. CSS Module을 배울 때 나온 일반 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다는 것이다.
예를 들어 다음 코드와 실행 결과를 확인해보자.
`hello ${{foo: 'bar' }} ${() => 'world'}!`
// 결과: "hello [object Object] () => 'world'!"
템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어 버리게 된다. 객체는 "[object Object]"로 변환되고, 함수는 함수 내용이 그대로 문자열화되어 나타난다.
만약 다음과 같은 함수를 작성하고 나서 해당 함수 뒤에 템플릿 리터럴을 넣어주면, 템플릿 안에 넣은 값을 온전히 추출할 수 있다.
function tagged(...args) {
console.log(args);
}
tagged`hello ${{foo: 'bar' }} ${() => 'world'}!`
크롬 브라우저의 개발자 콘솔에다 코드를 붙여 넣으면 다음과 같은 결과가 나타난다.
styled-components를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현한다.
예시 코드
import styled from 'styled-components'; const MyComponent = styled.div` font-size: 2rem; `;
이렇게 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어주면, 해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성된다. 그래서 나중에 Hello와 같은 형태로 사용할 수 있다.
div가 아닌 button이나 input에 스타일링을 하고 싶다면 styled.button 혹은 styled.input 같은 형태로 뒤에 태그명을 넣어주면 된다.
하지만 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현할 수 있다.
// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
background: gray;
`
// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)`
color: blue;
`
styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있다. 이전에 작성했던 Box 컴포넌트를 다시 봐보도록 하자.
styledComponent.js - Box 컴포넌트
const Box = styled.div` /* props로 넣어 준 값을 직접 전달해 줄 수 있다. */ background: ${props => props.color || 'blue'}; padding: 1rem; display: flex; `;
이 코드를 보면 background 값에 props를 조회해서 props.color의 값을 사용하게 했다. 그리고 color 값이 주어지지 않을 때는 blue를 기본 색상으로 설정했다.
이렇게 만들어진 코드는 JSX에서 사용될 때 다음과 같이 color 값을 props로 넣어 줄 수 있다.
<Box color="black">(...)</Box>
일반 CSS 클래스를 사용하여 조건부 스타일링을 해야 할 때는 className을 사용하여 조건부 스타일링을 해왔는데, styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있다.
다시 위에서 작성한 Button 컴포넌트를 확인해보자.
import React from 'react';
import styled, { css } from 'styled-components';
const Box = styled.div`
(...) // 생략
`;
const Button = styled.button`
(...) // 생략
/* 다음 코드는 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에 따라 넣어 줘야 할 때는 CSS를 styled-components에서 불러와야 한다. CSS를 사용하지 않고 다음과 같이 바로 문자열을 넣어도 작동되기는 한다.
${props =>
props.inverted &&
`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
하지만 이렇게 했을 때 해당 내용이 그저 문자열로만 취급되기 때문에 VS Code 확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않는다는 단점이 따른다.
더욱 치명적인 단점은 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못 해 해당 부분에서는 props 값을 사용하지 못한다는 것이다. 만약 조건부 스타일링을 할 때는 넣는 여러 줄의 코드에서 props를 참조하지 않는다면 굳이 CSS를 불러와서 사용하지 않아도 상관없다. 하지만 props를 참조한다면, 반드시 CSS로 감싸줘서 Tagged 템플릿 리터럴을 사용해줘야 한다.
이번엔는 style-components를 사용할 때 반응형 디자인을 어떻게 하는지 한번 알아보자. 브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media query를 사용하면 된다.
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%;
}
`;
일반 CSS에서 할 때랑 큰 차이는 없지만 이러한 작업을 여러 컴포넌트에서 반복해야 한다면 귀찮아질 것이다. 그럴 때는 이 작업을 함수화하여 간편하게 사용할 수 있다. styled-components 매뉴얼에서 제공하는 유틸 함수를 따라 사용해보자.
import React from 'react';
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를 StyledComponent.js에서 만들었지만, 실제로 사용한다면 아예 다른 파일로 모듈화한 뒤 여기저기서 불러와 사용하는 방식이 훨씬 편할 것이다.
참고문헌
김민준,「리액트를 다루는 기술 :실무에서 알아야 할 기술은 따로 있다!」, 길벗, 개정판[실은 2판] 2019 (개정판)