React를 다루는 기술 11 - 컴포넌트 스타일링 - 2
CSS Module
CSS Module은 CSS를 불러와서 사용할 때 클래스명을 고유한 값([파일명]_[클래스명]_[해쉬값]
)으로 바꿔 클래스명이 중복되는 일을 방지해주는 기술이다.
Sass와 마찬가지로 create-react-app에서는 v2부터 바로 사용이 가능하며 이외에는 Webpack.config.js 파일에서 css-loader를 설정해줘야 사용가능하다.
/* CSSModule.module.css */
.wrapper {
background: black;
[...]
}
:global .something {
font-weight: 200;
}
CSS Module을 사용하는 경우에도 기본적인 CSS문법은 동일하며 대신 클래스명의 고유성에 대해서는 고려할 필요가 없어지는데 이는 해당 css 파일의 클래스명이 import 해온 컴포넌트 내에서만 사용되기 때문이다.
만약 웹페이지 전체에서 전역적으로 사용되게하고 싶다면 앞에 :global
을 붙여주면된다.
import React from 'react';
import styles from './CSSModule.module.css';
const App = () => {
return (
<div className={styles.wrapper}>
안녕하세요 저는 <span className="something">CSS Module</span>
</div>
)
}
작성한 CSS Module을 컴포넌트에 적용하고 싶을 때에는 클래스명에 {style.클래스명}
을 입력해주면 된다.
그러면 이 클래스명은 자동으로 고유한 값([파일명]_[클래스명]_[해쉬값]
)으로 입력되며 import 해온 css 파일의 클래스명과 매칭되어 사용된다.
전역적으로 사용하기 위해 :global
을 붙였을 경우에는 이전과 동일하게 그냥 클래스명을 문자열로 입력해주면 된다.
import React from 'react';
import styles from './CSSModule.module.css';
const App = () => {
return (
<div className={`${styles.wrapper} ${styles.inverted}`}>
안녕하세요 저는 <span className="something">CSS Module</span>
</div>
)
}
만약 2개 이상의 클래스명을 적용할 때에는 위처럼 (``)로 감싸서 입력해주면 되며 이를 ES6 문법인 템플릿 리터럴이라고 한다.
템플릿 리터럴을 사용하면 기존에 [문자열] + [변수] + [문자열]
과 같이 문자열과 변수를 따로 작성하여 +로 연결해줘야했던 형태를 개선하여 ``안에 문자를 입력하고 변수를 넣고 싶을때는 ${[변수명]}
과 같은 형태로 입력해주면 된다.
classnames
classnames는 CSS 클래스를 조건부로 사용하고 싶을 때 매우 유용한 라이브러리이다.
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'
이런식으로 여러가지 변수를 조합하여 클래스명을 설정할 수 있다.
props에 어떤 값이 들어오냐에 따라 스타일에 변화를 줘야할 때 유용하며 같은 결과를 내기 위해 삼항연산자 등의 긴 코드를 작성해야하는 부분을 간단하게 한 단어로 처리할 수 있어 가독성에도 도움을 준다.
이 classnames를 이용하면 위에서 언급했던 CSS Module의 사용도 간편해지는데 기본적인 작성법이던 style.[클래스명]
을 유지할 필요가 없어진다.
import classNames from 'classnames/bind';
import styles from './CSSModule.module.css';
const cx = classNames.bind(styles);
const App = () => {
return (
<div className={cx('wrapper', 'inverted')}>
안녕하세요 저는 <span className="something">CSS Module</span>
</div>
)
}
이렇게 classNames.bind 함수를 통해 미리 styles 클래스를 받아오도록 하고 해당 클래스에 클래스명을 문자열로 넣어주면 된다.
이를 통해 style.
을 붙여야 할 필요가 없어졌으며 클래스명이 1개이든 2개이든 동일한 방식으로 코드 작성이 가능해져 가독성에도 도움이 된다.
Sass에서 CSS Module 사용하기
Sass를 사용할 때도 파일명 확장자를 module.scss
로 달아주면 CSS Module로써 사용할 수 있다.
/* CSSModule.module.scss */
.wrapper {
background: black;
&.inverted {
color: black;
background: white;
}
}
:global {
.something {
font-weight: 200;
}
}
기본적으로 사용하는 방법은 이전의 scss나 CSS Module과 동일하며 다만 전역적으로 사용하고 싶을 때는 scss 문법에 맞추어 {}
로 감싸주게 된다.
만약 CSS Module 파일이 아닌 일반 scss 파일에서 CSS Module을 사용하고 싶다면 역으로 :local
을 붙여주면 된다.
/* CSSModule.scss */
:local {
.wrapper {
background: black;
&.inverted {
color: black;
background: white;
}
}
}
.something {
font-weight: 200;
}
styled-components
styled-components는 이제까지 살펴본 방식과는 근본적으로 차이가 있는데 이 방식은 css 파일을 따로 작성하지 않는다.
대신 js파일에 css를 작성하여 style을 적용하는 방식을 사용한다.
이러한 방식을 CSS-in-JS
라고 부르며 이를 지원하는 많은 라이브러리가 존재하는데 여기선 그 중 가장 범용적으로 사용되는 styled-componenets를 알아보고자 한다.
import React from 'react';
import styled, { css } from 'styled-components';
const Box = styled.div`
/* props를 넣어 값을 직접 전달할 수 있다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
`;
const Button = styled.button`
background: white;
color: black;
border-radius: 4px;
[...]
/* &문자를 사용하여 Sass처럼 자기 자신 선택 가능 */
&:hover {
background: blue;
}
/* props.inverted의 값이 true일때 적용 */
${props =>
props.inverted &&
css`
background: none;
border: 2px solid white;
&:hover {
background: white;
}
`}
`
const App = () => {
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
}
- js 내부에서 스타일링이 모두 가능
- props를 전달하여 상태에 따른 변화를 구현가능
- Sass 문법도 지원
styled-components를 사용하면 이러한 장점을 이용할 수 있다.
그렇다면 styled-components는 js 파일 내에서 작성한 코드를 어떻게 스타일로써 적용시킬 수 있는 것일까.
이는 코드 시작과 끝에 들어가는 `을 이용한 Tagged 템플릿 리터럴을 이용한 것이다.
Tagged 템플릿 리터럴은 일반적인 템플릿 리터럴과 비슷해보이지만 자바스크립트의 객체나 함수를 온전히 전달할 수 있다는 큰 차이점이 존재한다.
`hello $ ${() => 'world'}!`
// 결과 : "hello [object Object] () => 'world'!"
일반적인 템플릿 리터럴을 사용할 경우에는 위처럼 맵은 Object로 함수는 문자열 그대로 표시되어 전달이 불가능하다.
function tagged(...args) {
console.log(args);
}
tagged`hello $ ${() => 'world'}!`
하지만 위처럼 함수를 작성하여 그 함수 뒤에 리터럴을 붙여주면 리터럴 안의 값은 함수로 온전하게 전달되게 된다.
이를 통해 props를 이용한 변수 및 함수를 스타일 내에 넣어도 최초에 태그로 붙여준 styled.div
가 함수로써 템플릿 리터럴 내의 값들을 처리해주어 구현이 가능한 것이다.
const Button = styled.button`
background: white;
color: black;
border-radius: 4px;
[...]
/* &문자를 사용하여 Sass처럼 자기 자신 선택 가능 */
&:hover {
background: blue;
}
/* props.inverted의 값이 true일때 적용 */
${props =>
props.inverted &&
css`
background: none;
border: 2px solid white;
&:hover {
background: white;
}
`}
`
Button 컴포넌트에 있던 css가 붙은 템플릿 리터럴도 이와 같은 원리이다.
여기서 일반 템플릿 리터럴을 사용할 경우에도 작동은 하지만 VS Code에서 작업할 경우 일반 문자열로 취급해 하이라이팅이 이루어지지 않으며 내부에서 props 값을 사용할 수 없어진다.
때문에 styled 내부에서 함수를 사용하여 템플릿 리터럴을 작성하는 경우에도 꼭 css를 태그로 붙여줘야한다.
출처자료
https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=209354690 (리액트를 다루는 기술(개정판))