리액트에서는 컴포넌트를 스타일링 할 때 다양한 방식을 사용 할 수 있습니다. 이 튜토리얼에서는 어떤 방식이 있는지, 자주 사용되는 것들을 하나하나 사용해보겠습니다.
저는 개인적으로 컴포넌트 하나마다 Sass 파일 하나씩 만들어서 관리를 하는것을 선호하고, 최근 만드는 프로젝트에서는 styled-components 를 사용하기도 합니다 :) CSS Module 은 작년에 자주 사용했었는데 사용하기가 요즘은 조금 불편하다고 느껴져서 잘 사용하고 있지 않고 있습니다.
이 포스트는 옛날에 제 블로그에 작성한 리액트 컴포넌트 스타일링 을 최신화한 포스트 입니다. CRA 버전이 달라져서 webpack 설정 부분에 변화가 있었습니다. 추가적으로, 이전에 집필한 리액트를 다루는 기술 에서 다룬 내용에서도 CRA 가 업데이트됨에 따라 설정방식이 달라졌는데 9장 컴포넌트 스타일링 대신 이 튜토리얼을 진행하시면 도움이 될 것입니다. 만약에 책이랑 똑같은 방식으로 진행을 하시려면 CRA 1.1.5 버전을 사용하셔야됩니다 (참고)
이 튜토리얼에선 create-react-app 을 사용하여 프로젝트를 직접 만들어서 진행하도록 하겠습니다.
$ npx create-react-app styling-react
기존의 CSS 스타일링에 있어서 딱히 불편함을 느끼지 않고, 새로운 기술을 배우는게 불필요하다고 생각한다면 그냥 CSS 를 사용하시면 됩니다.
실제로, 그냥 작은 프로젝트라면 다른 시스템을 도입하는것 조차 고민하기도 귀찮습니다. 그런 상황에는 프로젝트에 기본적으로 적용되어있는 CSS 시스템을 사용하는것만으로도 충분합니다.
create-react-app 으로 만든 프로젝트를 보면, src 디렉토리에 App.js 와 App.css 가 있습니다.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
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 {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
.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 Selector 의 활용입니다.
create-react-app 으로 만든 App 컴포넌트를 보면, 클래스명이 컴포넌트명-클래스
으로 네이밍이 되어있습니다.
이런 형식 외에도, BEM Naming 이란 방식도 있는데, 컴포넌트마다 CSS 파일을 하나하나 만드는 상황에선 그렇게 권장되지는 않습니다. BEM 은 스타일을 하나의 파일에 몰아서 작성 할 때는 편리하지만, 클래스 파일이 여러개일때는 딱히 이렇게까지 이름을 지을 필요는 없다고 생각합니다.
다른 방식으로는, CSS Selector 를 활용하는 것 입니다. 예를들어서 위 CSS 를 다음과같이 변환을 하면:
.App {
text-align: center;
}
/*.App 안에 들어있는 .logo*/
.App .logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
/* .App 안에 들어있는 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);
}
}
App.js 에서 사용된 클래스명들은 다음과 같이 수정해주면 됩니다.
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
class App extends Component {
render() {
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
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
}
export default App;
이런식으로, 컴포넌트의 최상위 html 요소에는 컴포넌트 이름과 동일한 이름으로 클래스명을 지으시고, 그 하단에서는 소문자로 입력하거나, 딱히 클래스명이 불필요한 경우엔 아예 생략을 할 수도 있죠.
Sass (Syntactically Awesome Style Sheets: 문법적으로 짱 멋진 스타일시트) 는 CSS pre-processor 로서, 복잡한 작업을 쉽게 할 수 있게 해주고, 코드의 재활용성을 높여줄 뿐 만 아니라, 코드의 가독성을 높여주어 유지보수를 쉽게해줍니다.
이전 버전의 create-react-app 으로 만든 프로젝트에서는 Sass 를 사용하기위하여 별도의 작업을 추가적으로 해줬어야 하는데, v2 다음 버전부터는 그냥 바로 사용하실 수 있습니다.
Sass 에서는 두가지의 확장자 (.scss/.sass) 를 지원합니다. Sass 가 처음 나왔을떈 sass 만 지원되었고, sass 는 문법이 아주 다른데요:
$font-stack: Helvetica, sans-serif
$primary-color: #333
body
font: 100% $font-stack
color: $primary-color
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
더 많은 차이점들은 여기 서 비교해볼 수 있습니다. 보통 scss 문법이 더 많이 사용되므로, 우리는 .scss 확장자로 스타일을 작성하겠습니다.
한번 새 컴포넌트를 만들어서 Sass 를 사용해볼게요!
우선, node-sass 라는 라이브러리를 설치해야합니다. 이 라이브러리는 Sass 를 CSS 로 변환해줍니다.
$ yarn add node-sass
다음, src 디렉토리에 SassComponent.scss 파일을 생성하세요.
// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// mixin 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
@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;
}
}
}
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.js 에서 렌더링하세요:
import React, { Component } from "react";
import SassComponent from "./SassComponent";
class App extends Component {
render() {
return <SassComponent />;
}
}
export default App;
자주 사용 될 수도 있는 Sass 변수 및 믹스인을 따로 파일로 분리해보겠습니다.
src 디렉토리 안에 styles 라는 디렉토리를 생성하고, 그 안에 utils.scss 파일을 만들어서, 기존 SassComponent 스타일 코드에 작성됐던 변수들과 믹스인을 잘라내서 이동시키겠습니다.
// 변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
// mixin 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
이제, SassComponent.scss 에서 위 파일에서 선언한 것들을 사용해보겠습니다. 다른 scss 파일을 불러올 땐 @import
구문을 사용합니다.
@import './styles/utils.scss';
.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;
}
}
}
동일하게 작동하는지 확인해보세요!
이 부분은 Sass 를 사용 할 때 필수적인 작업은 아니지만, 해두면 좋을 수도 있는 작업입니다! 예를들어서, 방금 우리가 SassComponent 에서 styles/utils.scss 를 불러올 때, @import '../styles/utils.scss';
이런식으로 구현을 했었는데요, 만약에 프로젝트에 디렉토리를 깊숙한 구조로 만든다면, (예: src/components/somefeature/ThisComponent.js) import '../../../styles/utils.scss'
이런식으로 한참 거슬러올라가야 한다는 단점이 있습니다.
이 문제점를 핵려하는 방법은 Webpack에서 Sass 를 처리하는 sass-loader 의 설정을 커스터마이징하여 해결 할 수 있습니다. 그렇지만, create-react-app 으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위하여 세부 설정들이 모두 숨어있습니다. 이를 커스터마이징 하기 위해서는 yarn eject
명령어를 통하여 세부설정을 다시 밖으로 꺼내주어야 합니다.
create-react-app 으로 만든 프로젝트는 기본적으로 .git 설정도 되어있는데요, yarn eject 는 아직 git 에 커밋되지 않은 변화가 있다면 진행이 되지 않으니, 먼저 커밋을 해주어야 합니다.
VSCode 의 git UI 를 사용하셔서 현재까지 한 작업을 커밋하시거나, 다음 명령어를 통해서 커밋을 하세요.
$ git add .
$ git commit -m'Commit before yarn eject'
그리고 나서, yarn eject 명령어를 실행합니다.
$ yarn eject
yarn run v1.12.0
warning ../package.json: No license field
$ react-scripts eject
? Are you sure you want to eject? This action is permanent. (y/N) y
이제 config 경로가 프로젝트에 생겼을텐데요, 그 안에 webpack.config.js 를 열어보세요.
그리고, sassRegex 를 찾아보시면
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
sideEffects: true,
},
이런 블록이 보일텐데, 여기서 use:
부분을 문자열을 다음과 같이 교체하세요:
{
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
}
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true
},
getStylesLoaders 에서 두번째 파라미터로는 문자열형태로밖에 받아오지 못하니, 위와 같은 형태로 getStyleLoaders 로 만든 배열 뒷부분에다가 우리가 사용할 loader를 options 와 함께 적용을 해주었습니다.
작성 후, 서버를 껐다가 재시작하세요.
이제, utils.scss 파일을 불러올 때, 현재 수정하고 있는 scss 파일 경로가 어디에 위치하더라도, 앞부분에 상대경로를 입력 할 필요 없이 styles 디렉토리 기준 절대경로로 불러올 수 있습니다.
@import 'utils.scss';
한번, SassComponent.scss 파일에서 기존 import 구문을 위와 같이 수정해보고 적용이 잘 되는지 확인해보세요.
이제 utils 를 사용 하는 컴포넌트가 있다면 위 한줄만 붙여넣어주시면 됩니다.
하지만! 이렇게 utils.scss 를 포함시키는것 조차 귀찮을 수도 있을 것 입니다. 그럴땐, sass-loader 의 data 설정을 사용하시면 됩니다. data 속성은, Sass 파일을 읽을 때 마다 앞부분에 특정 코드를 포함시켜줍니다.
webpack.config.js 를 열어서 data 를 포함시켜보세요:
{
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,
data: `@import 'utils';`
}
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true
},
이렇게 하고 나면, 모든 scss 파일에서 utils 를 사용 할 수 있게 되므로, SassComponent 에서 맨 위 import 구문을 지워도 정상적으로 작동 할 것입니다.
yarn혹은 npm 을 통하여 설치한 Sass 라이브러리를 불러오는 방법을 알아봅시다. 가장 기본적인 방식으로는
@import '../../../node_modules/library/styles';
과 같은 형식으로 위로 거슬러 올라가주어야 합니다. 하지만, 이것보다 더욱 쉬운 방법이 있는데요, 바로 물결 ~
를 사용 하는 것 입니다.
@import '~library/styles';
제가 자주 사용하는 Sass 라이브러리중에서는 반응형 디자인을 쉽게 해주는 include-media 와 색상 팔레트인 open-color 가 있는데요, 만약 이를 사용하게 된다면, 다음과 같이 불러오면 됩니다.
$ yarn add open-color include-media
@import '~include-media/dist/include-media';
@import '~open-color/open-color';
패키지 디렉토리 안에 있는 scss 파일을 불러와야 하므로, node_modules 안으로 들어가서 디렉토리를 확인 후 필요한 파일을 불러오시면 됩니다.
CSS Module 은 CSS 클래스를 불러와서 사용 할 때 [파일이름]_[클래스이름]__[해쉬값] 형태로 클래스네임을 자동으로 고유한 값으로 만들어주어서 컴포넌트 스타일 중첩현상을 방지해주는 기술입니다. 이를 사용하기 위해선, [파일이름].module.css 이런식으로 파일을 저장하셔야 합니다.
한번, CSSModule.module.css 라는 스타일을 먼저 작성해봅시다.
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
/* 글로벌 CSS 를 작성하고 싶다면 */
:global .something {
font-weight: 800;
color: aqua;
}
자바스크립트로 컴포넌트도 작성해볼까요?
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;
위 코드처럼 styles 를 불러오면 하나의 객체를 전달받게 되는데 그 안에는 CSS Module 에서 사용한 클래스 이름과, 해당 이름을 고유화시킨 값이 key-value 형태로 들어있습니다.
한번 console.log(styles) 를 하면 이런 결과가 나타납니다:
{
wrapper: "CSSModule_wrapper__CUMkx"
}
그리고, 이걸 사용하기 위해선 className={styles.[클래스이름]}
형태로 설정을 해주시면 됩니다.
다 작성하셨으면 App 컴포넌트에서 CSSModule 컴포넌트를 렌더링해봅시다.
import React, { Component } from 'react';
import CSSModule from './CSSModule';
class App extends Component {
render() {
return <CSSModule />;
}
}
export default App;
이런식으로, 잘 작동하나요?
만약에 CSS Module 을 사용한 클래스이름을 두개 이상 적용 할 때는 이렇게 하시면 됩니다:
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
.inverted {
color: black;
background: white;
}
/* 글로벌 CSS 를 작성하고 싶다면 */
:global .something {
font-weight: 800;
color: aqua;
}
import React from 'react';
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;
CSS Module 이 잘 작동하고 있나요?
classNames 는 CSS 클래스를 조건부로 설정 할 때 매우 유용한 라이브러리입니다. 그리고, CSS Module 을 사용 할 때 이 라이브러리를 함께 사용 한다면 여러 클래스를 적용할 때 편해집니다.
우선, classNames 를 설치하세요:
$ yarn add classnames
한번, 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 이나 false 에 따라 highlighted 라는 클래스가 적용 될 것이고, 추가적으로 theme 으로 전달받는 문자열이 그대로 클래스에 적용 될 것입니다.
만약에 이런 라이브러리의 도움을 받지 않는다면 이런 형식으로 처리를 하셔야 될 겁니다:
const MyComponent = ({ highlighted, theme }) => {
<div className={`MyComponent ${theme} ${highlighted ? 'highlighted' : ''}`}>Hello</div>
}
classNames 를 쓰는 것이 훨씬 가독성이 높지요?
추가적으로, CSS Module 과 함께 쓸 땐 어떻게 편해질 수 있는지 알아보겠습니다.
classNames 를 불러올때 classnames/bind
를 사용하면 클래스를 넣어줄 때 마다 styles.[클래스]
형식으로 할 필요 없이, 사전에 미리 styles 에서 받아와서 사용하게끔 설정해두고 cx('class1', 'class2')
형태로 사용 할 수 있게 됩니다.
한번 CSSModule.js 컴포넌트를 다음과 같이 작성해보세요:
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>
);
};
export default CSSModule;
classnames/bind 를 사용하면, CSS Module 을 사용 할 때 클래스를 여러개 설정하거나 또는 조건부로 설정을 하게 될 때, 훨씬 편하게 작성 할 수 있겠죠?
Sass 를 사용할때도 파일이름 뒤에 .module.scss
을 입력해주면 CSS Module 로 사용 할 수 있습니다.
한번 파일 이름을 변경해보세요. 스타일 코드도 조금 바꾸겠습니다.
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.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;
}
// 여기에 다른 클래스를 만들 수도 있겠죠?
}
그리고 CSSModule.js 에서도 상단에 .css 파일 대신 .scss 파일을 불러오세요.
import styles from './CSSModule.module.scss';
이전과 똑같이 보여지고 있나요?
우리가 CSS Module 에서 글로벌 클래스를 정의 할 때 :global
을 썼었던 것 처럼, CSS Module 이 아닌 일반 .css/.scss 파일에서도 :local
을 통하여 CSS Module 을 사용 할 수도 있습니다.
:local .wrapper {
/* 스타일 */
}
:local {
.wrapper {
/* 스타일 */
}
}
styled-components 는 현존하는 리액트 CSS-in-JS 관련 라이브러리 중에서 가장 잘나가는 라이브러리입니다. CSS-in-JS 는 이름이 그렇듯, 자바스크립트 파일 안에 CSS 를 작성하는 형태입니다. 해외의 큰 기업 - Atlassian, Reddit, coinbase 등에서도 사용되고 있고, 국내에서도 사용하는곳이 꽤 있습니다 - Channel.io, Huiseoul, Tumblebug 등..
styled-components 의 대체제는 현재 대표적으로 emotion 이 있습니다. 작동 방식은 꽤나 비슷합니다.
취향에 따라 갈릴수도 있지만, 분명히 알아두면 좋은 라이브러리인건 확실합니다.
한번 사용해볼까요?
$ yarn add styled-components
이 라이브러리를 통해 한번 예제 컴포넌트를 만들어보겠습니다. 그냥 하나의 자바스크립트 파일안에 스타일까지 작성 할 수 있기 때문에 .css/.scss 파일 같은걸 만들 고민은 안하셔도 된다는게 큰 이점입니다.
한번 예제 컴포넌트 코드를 작성해보세요:
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;
styled-components 에서는 스타일을 입력 할 때 Tagged 템플릿 리터럴(Template Literal) 이라는 ES6 문법을 사용합니다. 이 문법을 사용하는 이유는, `` 를 사용할 때 내부에 JavaScript 객체나 함수가 전달 될 때 이를 따로 추출하기 위함입니다.
예를들어서:
`hello ${{foo: 'bar' }} ${() => 'world'}!`
// 결과: "hello [object Object] () => 'world'!"
위 코드는 [object Object]
이런식으로 문자열로 들어가게되면서 형태를 잃어버리게 되는데요, 만약에 함수를 다음과 같이 만들어서 사용하면 이 템플릿 리터럴 안에 넣어준
값들을 온전히 알아낼 수 있게 됩니다.
function tagged(...args) {
console.log(args);
}
tagged`hello ${{foo: 'bar' }} ${() => 'world'}!`
이렇게, 사이사이에 들어가는 JavaScript 값의 원본 값을 그대로 추출 할 수 있습니다.
스타일링 된 엘리먼트를 만들 땐, 상단에서 styled 를 불러오고 styled.태그명
을 사용하여 구현합니다:
import styled from 'styled-components';
const MyComponent = styled.div`
font-size: 2rem;
`;
저 자리에 button
이던, input
이던, 원하는걸 넣으시면 됩니다.
하지만, 만약에 보여줘야 할 태그 형식이 유동적이거나, 아니면 특정 컴포넌트에 스타일링을 해야 하는 상황이라면 다음과 같은 형태로 구현 할 수 있습니다:
// 문자열로 styled 의 인자로 전달
const MyInput = styled('input')`
background: gray;
`
// 아예 컴포넌트 형식의 값을 넣어줌
const StyledLink = styled(Link)`
color: blue;
`
스타일링 한 컴포넌트에 전달하는 props 값을 스타일쪽에서 그대로 사용 하실 수 도 있습니다.
const Box = styled.div`
/* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`;
일반 CSS 클래스를 사용했더라면 주로 클래스이름으로 조건부 스타일링을 해왔었을텐데요, styled-components 에서는 그냥 props 로도 처리 가능합니다. 이렇게 말이죠:
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;
}
`;
한번 App에서 렌더링해볼까요?
import React, { Component } from 'react';
import StyledComponent from './StyledComponent';
class App extends Component {
render() {
return <StyledComponent />;
}
}
export default App;
styled-components 에서 반응형 디자인은 어떻게 하는지 알아봅시다. 일단, 일반 CSS 랑 똑같이 하면 되긴 하는데요:
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%;
}
`;
이런 작업을 함수화하여 훨씬 더 쉽게 할 수도 있답니다:
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: 768px;`};
`;
어떤가요? 훨씬 간단해졌죠? 지금은 media 를 StyledComponent.js 에서 만들어주긴 했지만, 실제로 사용한다면 아예 다로 다른 파일로 분리시켜서 여기저기서 불러와서 사용하는게 훨씬 편할 것입니다.
styled-components 는 정말 유용한 라이브러리이고 프로덕션 레벨에서도 사용가능한 충분히 성장된 라이브러리입니다.
글로벌 색상 관리를 도와줄 수 있는 Theme 이라는 기능도 있는데, 저는 딱히 엄청 선호하지는 않습니다. 단순히 자주사용되는 색상들을 담기 위한 것 이라면 그냥 객체형태로 저장을 해두고 불러와서 사용하는것이 훨씬 간단하며, 이 기능은 사용자가 서비스의 테마를 직접 변경 할 수도 있는 경우에 사용하면 조금 유용 할 수도 있습니다.
서버 사이드렌더링 도 지원이 되고있습니다.
정말 다양한 리액트 컴포넌트 스타일링 방식을 배워보았습니다. 모두 쓸모있는 기술들이며, 이러한 방식들 중에서 뭘 사용 할 지 선택하는것은 여러분의 몫입니다.
git add . git commit -m 'Commit before yarn eject' 을 해도 에러가 나는 경우는 어떻게 해결을 해야할까요..?
감사합니다. 책보다가 막혀서 일러주신 부분 보고 어느정도 해결은 한 것 같습니다만, 사소한 미해결 궁금증이 남아서 https://github.com/velopert/learning-react/issues/96 에 질문 드렸습니다.
혹시 styled-components 에서 기존 사스 함수를 쓰려면 어떻게 해야될까요?
단위 변환하는 식이 있어서
const StyledLogo = styled(ReactLogo) height: 25rem; width: 25rem; display: inline-block; margin: auto; .stroke { stroke : ${props => props.stroke} }
;
여기에서 기존엔 height 와 width를
@include function-name(width, 25px); 이런식으로 썼었어요.
많은 도움 되고 있습니다.
한가지 궁금한게 있는데요,
상황에 따라서 쓰고 싶은데 scss와 module.scss를 같이 쓰고 싶은데
scss와 module.scss을 같이 사용하는건 불가능한가요?
웹팩에서 css-loader modules설정을 켜면 className="" 이 안먹고
설정을 끄면 className={}이 작동하지 않습니다.
한가지 더 module.scss로 저장을 안해도 .scss에서 모듈 작동이 되던데 제가 설정을 잘못한걸까요?
안녕하세요. 정말 잘 보고 배우고 있습니다.
잘 보고 따라하다가 sass-loader 설정에서 오류가 생겨서 댓글로 남깁니다.
==변경전==
loader: require.resolve('sass-loader'),
options: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap
}
==변경후==
loader: require.resolve('sass-loader'),
options: {
sassOptions: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap
}
}
options object 에서 includePaths property를 인식하지 못하는 문제 였습니다.
변경 후는 sassOptions object로 넣어 주시면 됩니다.
'sass-loader 설정 커스터마이징 하기' 에서 data 프로퍼티를 추가하여 특정 코드를 포함시켜주는 부분에 수정이 필요합니다.
현재는 'data'가 아닌 'prependData' 로 입력을 해줘야지 정상 동작합니다.
코드로 보면
options: {
prependData: "@import 'utils';",
sassOptions: { ... },
}
로 입력을 해줘야 제대로 동작 합니다.
1개의 github 저장소에서 3개의 프로젝트를 작업합니다......
next.js 를 사용한다고 하며 scss는 하나만 뱉는다구 하네요.... @_@;;; 어떡하지....
.pc {
@import './_pc.scss'
}
.mobile {
@import './_mobile.scss'
}
이렇게 해야하나 -ㅠ-
<sass-loader 설정 커스터마이징하기> sass-loader 의 data 설정 부분에서 prependData로 바꿔주어도 현 버전에서는 안되는 것 같습니다.
저는 additionalData로 해서 해결하였습니다.
use: getStyleLoaders({
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
}).concat({
loader: require.resolve('sass-loader'),
options: {
sassOptions: {
includePaths: [paths.appSrc + '/style'],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
additionalData:"@import 'utils';",
}
}),
sideEffects: true,
},
${props =>
props.inverted && // colon expected
css background: none; border: 2px solid white; color: white; &:hover { background: white; color: black; }
};
& + button {
margin-left: 1rem; // semi-colon expected
} // at-rule or selector expected
`;
전 왜 여기서 에러가 3개나 뜰까요..ㅠㅠ 도와주세요 ㅠ0ㅠ
좋은 정보 감사합니다. 한가지 질문 드려요
react (js) 로 module (library) 형태로하여 yarn build 후 dist/index.js를 배포합니다.
이때 exsample.css도 선언 되어 있지만 정작 dist/index.js에는 선언된 css가 없습니다.
bundlder({
cwd: process.cwd(),
define,
compress: false,
sourcemap: true,
smartAssetOption: {
url: 'inline',
// maxSize: 14 // Max file size to inline (in kb) default 14kb
},
format: 'cjs',
'css-modules': 'custom__[local]'
이 옵션으로 하게 되는데, 문제가 어디에 있을까요
안녕하세요! sass-loader 커스터 마이징중 오류가 발생했는데 해결 방법을 몰라 여쭙니다 ㅠㅠ
분명 책에 나와있는대로 수정을 진행하고 서버를 재시작했는데
SassError: Can't find stylesheet to import. << 이런 에러메세지와 함께 컴파일 오류가 나네요ㅜㅜ
제가 뭘 잘못하고 있는걸까요??
도움을 주시면 정말 감사하겠습니다.
webpack.config.js 코드
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders({
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
}).concat({
loader: require.resolve("sass-loader"),
options: {
sassOptions: {
includePaths: [path.appSrc + "/styles"],
},
sourceMap: isEnvProduction && shouldUseSourceMap,
additionalData: @import 'utils';
,
},
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
단순히 자주 사용하는 색상을 저장하는 것이 목적이라면 색상을 오브젝트로 저장하고 불러와서 사용하는 것이 훨씬 쉽습니다. https://geeksnation.net/gmail-sign-up
감사합니다! 드디어 utils를 간단하게 임포트 할 수 있게되었네요 :D