CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉
[파일 이름]_[클래스 이름]__[해시값]
형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술
.module.css
확장자로 파일을 저장하기만 하면 CSS Module이 적용된다.
/* CSSModule.module.css */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
/* 글로벌 CSS : 특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우*/
:global .something {
font-weight: 800;
color: aqua;
}
CSSModule을 사용하면 클래스 이름을 지을 때 고유성에 대해 고민하지 않아도 된다. 해당 클래스는 위에서 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동한다.
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이 적용된 스타일 파일(CSSModule.module.css
)을 불러오면 객체(styles
)를 하나 전달받게 되는데 CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있다.
console.log(styles);
의 결과 :
이 고유한 클래스 이름을 사용하려면 클래스를 적용하고 싶은 JSX 엘리먼트에 className={styles.[클래스 이름]}
형태로 전달해 주면 된다.
:global
을 사용하여 전역적으로 선언한 클래스는 그냥 문자열로 넣으면 된다.
CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는 다음과 같이 사용한다.
<div className={`${styles.wrapper} ${styles.inverted}`}>
ES6 문법 템플릿 리터럴을 사용해 문자열을 합해 주는 방식이다.
만약 템플릿 리터럴 분법을 사용하고 싶지 않다면 다음과 같이 작성할 수도 있다.
className={[styles.wrapper, styles.inverted].join(' ')}
classnames
는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리이다. 여러 클래스를 적용할 때 매우 편리하다.
$ yarn add classnames
또는 $ npm install 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 클래스를 설정할 수 있기 때문에 컴포넌트에서 조건부로 클래스를 설정할 때 매우 편리하다.
const MyComponent = ({ highligthed, theme }) => (
<div className={classNames('MyComponent', { highlighted }, theme)}> Hello </div>
만약 위에서 highlighted
값이 true
이면 highlighted 클래스가 적용되고, false
이면 적용되지 않는다.
또한 CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 bind
함수를 사용하면 클래스를 넣어 줄 때마다 styles.[클래스 이름]
형태를 사용할 필요가 없다.
사전에 미리 styles에서 받아 온 후 사용하게끔 설정해 두고 cx('클래스 이름', '클래스 이름2')
형태로 사용할 수 있다.
위에서 CSS Module을 사용한 코드에 bind 함수를 적용한 예를 보면 다음과 같다.
import React from "react";
import classNames from "classnames";
import styles from "./CSSModule.module.css";
const cx = classNames.bind(styles); // 미리 styles에서 클래스를 받아 오도록 설정
const CSSModule = () => {
console.log(styles);
return (
<div className={cx("wrapper", "inverted")}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
Sass를 사용할 때도 파일 이름 뒤에 .modules.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;
}
}
CSS Module에서 글로벌 클래스를 정의할 때 :global
을 사용했던 것처럼 일반 .css/.scss
파일에서도 :local
을 사용하여 CSS Module을 사용할 수 있다.
:local .wrapper {
/* 스타일 */
}
:local {
.wrapper {
/* 스타일 */
}
}
컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 파일 안에 스타일을 선언하는 방식이다.
이 방식을 'CSS-in-JS`라고 부르고 이와 관련된 라이브러리는 정말 많다.
CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 것이 styled-components이다.
설치
$ yarn add styled-components
또는 $ npm install styled-components
styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.
또한 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다.
// 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 StyledComponents = () => {
return (
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
);
};
export default StyledComponents;
위에서 작성한 코드를 보면 스타일을 작성할 때 `을 사용하여 만든 문자열에 스타일 정보를 넣어 주었는데, 이 문법을 Tagged 템플릿 리터럴 이라고 부른다.
CSS Module을 배울 때 나온 일반 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다는 것이다.
`hello ${{foo: 'bar' }} ${() => 'world'}!`
// 결과: "hello [object Object] () => 'world'!"
템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어 버리게 된다. 객체는 "[object Object]"로 변환되고, 함수는 함수 내용이 그대로 문자열화되어 나타난다.
만약 다음과 같은 함수를 작성하고 나서 해당 함수 뒤에 템플릿 리터럴을 넣어 준다면, 템플릿 안에 넣은 값을 온전히 추출할 수 있다.
function tagged(...args) {
console.log(args);
}
tagged `hello ${{foo: 'bar' }} ${() => 'world'}!`
Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이사이에 들어가는 자바스크립트 객체나 함수의 원본 값을 그대로 추출할 수 있다.
styled-components는 이러한 속성을 사용해 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해준다.
styled-components를 사용해 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled
를 불러오고 styled.태그명
을 사용하여 구현한다.
import styled from 'styled-components';
const MyCompoent = styled.div`
font-size: 2rem;
`;
styled.div
뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면, 해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성된다.
사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링을 해 주고 싶다면 다음과 같은 형태로 구현한다.
// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
background: gray;
`
// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)`
color: blue;
`
컴포넌트를 styled의 파라미터에 넣는 경우에는 해당 컴포넌트에
className
props를 최상위 DOM의 className값으로 설정하는 작업이 내부적으로 되어 있어야 한다.const Sample = ({ className }) => { return <div className={className}>Sample</div>; }; const StyledSample = styled(Sample)` font-size: 2rem; `;
스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있다.
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>
위에서 작성한 Button 컴포넌트는 다음과 같이 props
를 사용해 서로 다른 스타일을 적용할 수 있다.
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
import styled, { css } from "styled-components";
CSS를 사용하지 않고 다음과 같이 문자열을 넣어도 작동하기는 하지만 해당 내용이 그저 문자열로만 취급되기 때문에 신택스 하이라이팅이 제대로 이루어지지 않는다. 또 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props
값을 사용하지 못한다.
${(props) =>
props.inverted &&
`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
여러 줄의 코드에서
props
를 참조한다면 반드시 CSS로 감싸 주어 Tagged 템플릿 리터럴을 사용해야 한다.
브라우저 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media
쿼리를 사용한다.
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 매뉴얼에서 제공하는 유틸 함수를 따라 사용해보면 다음과 같다.
https://www.styled-components.com/docs/advanced#media-templates
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
를 StyledComponents.js에서 만들어 주었지만, 실제로 사용한다면 아예 다른 파일로 모듈화한 뒤 여기저기서 불러와 사용하는 방식이 편할 것이다.