리액트에서 컴포넌트를 스타일링 할 때는 다양한 방식을 사용할 수 있다.
(회사마다, 개발자마다, 각자 취향에 따라 선택한다.)
일반 CSS
: 컴포넌트를 스타일링하는 가장 기본적인 방식.Sass
: 자주 사용되는 CSS 전처리기(pre-processor) 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있다.CSS Module
: 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해주는 옵션.styled-components
: 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 도와준다.create-react-app으로 생성한 프로젝트는 일반 CSS 방식으로 만들어져 있다, 기존의 CSS 스타일링이 딱히 불편하지 않고 새로운 기술을 배울 필요가 없다고 생각되면, 일반 CSS를 계속 사용해도 상관 없다.
소규모 프로젝트를 개발하고 있다면 새로운 스타일링 시스템을 적용하는 것이 불필요할 수도 있다.
CSS를 작성할 때 가장 중요한 점은 CSS의 클래스를 중복되지 않게 하는 것이다.
CSS Selector를 사용하면 특정 클래스 내부에 있는 CSS클래스 에만 스타일을 적용 시킬 수도 있다.
.App .logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
Sass(Syntactically Awesome Style Sheets)는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높여줄 뿐 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 도와준다.
create-react-app 구버전에서는 Sass를 사용하려면 추가 작업이 필요하였으나, v2 버전 이후 부터는 별도의 추가 설정 없이 바로 사용이 가능하다.
.scss
와 .sass
두 가지 확장자를 지원.sass
확장자는 중괄호{} 와 세미콜론;을 사용하지 않으나, .scss
확장자는 기존 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않다. 보통 .scss
문법이 더 자주 사용된다.
// .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;
}
$ yarn add sass
$ npm install node-sass
Sass 변수 및 믹스인은 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용하는 것이 좋다.
// 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;
}
// SassComponent.scss
@import './styles/utils.scss';
.SassComponent {
display: flex;
.box { // 일반 CSS에서는 .SassComponent .box 와 같음.
cursor: pointer;
transition: all 0.3 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 {
background: black;
}
}
}
설치
$ yarn add [라이브러리 이름]
불러오기
@import '../../../node_modules/library/styles';
@import '~library/styles';
// 물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉토리를 탐지하여 스타일을 불러올 수 있다.
예시
include-media: 반응형 디자인을 쉽게 만들어 줌
open-color: 색상 팔레트
설치
$ yarn add open-color include-media
불러오기
// utils.scss
@import '~include-media/dist/include-media';
@import '~open-color/open-color';
// 변수 사용하기
$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.scss
@import './styles/utils.scss';
.SassComponent {
display: flex;
background: $oc-gray-2;
@include media('<768px') {
background: $oc-gray-9;
}
.box { // 일반 CSS에서는 .SassComponent .box 와 같음.
cursor: pointer;
transition: all 0.3 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 {
background: black;
}
}
}
CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값
[파일이름]_[클래스 이름]__[해시값]
형태로 자동으로 만들어 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술.
구버전 create-react-app에서는 웹팩에서 css-loader 설정을 별도로 해주어야 했지만, v2 버전 이후부터는 .module.css
확장자로 파일을 저장하면 CSS Module이 적용.
ClassName={styles.[클래스 이름]}
형태로 전달.:global
을 사용하여 전역적으로 선언한 클래스의 경우 평상시 처럼 문자열로 입력.
/* CSSModule.module.css */
/* 자동으로 고유해 지므로, 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
/* 글로벌 CSS 작성시 */
:global .somethig {
font-weight: 800;
color: aqua;
}
// 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;
클래스를 적용하고 싶은 JSX 엘리먼트에 템플릿 리터럴(Template Literal)을 사용하여 입력.
/* CSSModule.module.css */
/* 자동으로 고유해 지므로, 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
.inverted {
color: black;
background: white;
border: 1px solid black;
}
/* 글로벌 CSS 작성시 */
:global .somethig {
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;
설치
$ 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로 전달받는 문자열은 내용 그대로 클래스에 적용된다.
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='somthing'>CSS Module!</span>
</div>
);
};
export default CSSModule;
위와 같이 classnames
에 내장되어 있는 bind
함수를 사용하면 클래스를 넣어 줄 때마다 styles.[클래스 이름]
형태를 사용할 필요가 없다.
사전에 미리 styles에서 받아 온 후 사용하게 설정해 두고 cx('클래스 이름', '클래스 이름2')
형태로 사용할 수 있다.
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 {
.something {
font-weight: 800;
color: aqua;
}
// 여기에 다른 클래스 작성 가능
}
CSS Module에서 글로벌 클래스를 정의할 때 :global
을 사용했던 것처럼 CSS Module이 아닌 일반 .css
, .scss
파일에서도 :local
을 사용하여 CSS Module을 사용할 수 있다.
:local .wrapper {
/* 스타일 */
}
:local {
.wrapper {
/* 스타일 */
}
}
자바스크립트 파일 안에 스타일을 선언(CSS-in-JS) 하는 방식이다.
CSS-in-JS 라이브러리 중 개발자들이 가장 선호하는 방식 이다.
자바스크립트 파일 하나에 스타일까지 작성하기 때문에 .css
또는 .scss
확장자의 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.
설치
$ yarn add styled-componets
간략한 사용법
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.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;
장점
props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다.
VS Code 사용시 추천 확장프로그램
vscode-styled-components: styled-components를 위해 내부에 작성한 스타일이 그저 문자열로 간주되어 코드 Syntax 하이라이팅이 제대로 이루어지지 않는 문제를 해결
styled-components에서 스타일을 작성할 때 `을 사용하여 만든 문자열에 스타일 정보를 넣어주었다. 여기서 사용한 문법을 Tagged 템플릿 리터럴이라고 한다.
일반 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출 할 수 있다는 것이다.
function tagged(...args) {
console.log(args);
}
tagged`hello ${{foo: 'bar'}} ${() => 'world'}!`
// 1. ['hello', '', '!']
// 2. {foo: 'bar'}
// 3. () => 'world'
styled-components를 사용하여 스타일링된 엘리먼트를 만드려면 컴포넌트 파일의 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현한다.
import styled from 'styled-components';
const MyComponent = styled.div`
font-size: 2rem;
`;
styled.div
뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면, 해당 스타일이 적용된 div
로 이루어진 리액트 컴포넌트가 생성되어 <MyComponent>Hello</MyComponent>
와 같은 형태로 사용할 수 있다.div
가 아닌 button
이나 input
에 스타일링을 하고 싶다면 styled.button
혹은 styled.input
같은 형태로 뒤에 태그명을 넣어주면 된다.
// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
background: gray;
`
// 컴포넌트 형식의 값을 넣어줌
const StyledLink = styled(Link)`
color: blue;
`
위에서 나오는 Link 컴포넌트는 리액트 라우터에서 사용할 컴포넌트이다. 이런 식으로 컴포넌트를 styled의 파라미터에 넣는 경우에는 해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정하는 작업이 내부적으로 되어있어야 한다.
const Sample = ({ className }) => {
return <div className={className}></div>
};
const StyledSample = styled(Sample)`
font-size: 2rem;
`
const Box = styled.div`
// props로 넣어준 값을 직접 전달해 줄 수 있다.
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
`
<Box color='black'>(...)</Box>
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;
}
`;
다음과 같이 props를 사용하여 서로 다른 스타일을 적용할 수있다.
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS
를 styled-components에서 불러와야 한다.
CSS를 사용하지 않고 바로 문자열을 넣어도 작동하기는 하지만, 해당 내용이 문자열로만 취급되기 때문에 VS Code 확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않고 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다.
조건부 스타일링을 할 때 props 참조하면 반드시 CSS로 감싸 주어서 Tagged 템플릿 리터럴을 사용해 주어야 한다.
일반 CSS를 사용할 때와 똑같이 media 쿼리(query)를 사용한다.
const Box = styled.div`
// props로 넣어 준 값을 직접 전달해 줄 수 있다.
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
// 반응형 가로 크기 조절
width: 1024px;
margin: 0 auto;
@media(max-width: 1024px) {
width: 768px;
}
@media(max-width: 768px) {
width: 100%;
}
`
여러 컴포넌트에서 반복 될 경우 함수화하여 간편하게 사용할 수 있다.
import styled, { css } from 'styled-components';
const sizes = {
desktop: 1024,
tablet: 768
};
// sizes 객체에 따라 자동으로 media 쿼리 함수를 만들어 준다.
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
@media(max-width: ${sizes[label] / 16}em) {
${css(...args)}
}
`;
}, {});
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
를 다른 파일로 모듈화한 후 불러와 사용하는 방식으로 효율적이게 사용할 수 있다.[참고] 김민준(Velopert)님의 '리액트를 다루는 기술'을 공부하며 정리한 내용임.