상세 컨텐츠

본문 제목

[리액트를 다루는 기술] 9장 컴포넌트 스타일링

21-22/21-22 리액트 스타터 -1

by Kimpeep 2022. 1. 3. 13:00

본문

728x90

컴포넌트를 스타일링할 때는 다양한 방식을 사용할 수 있지만, 이번에 다룰 스타일링 방식은 다음과 같습니다.

  • 일반 CSS : 컴포넌트를 스타일링하는 가장 기본적인 방식
  • Sass : 자주 사용되는 CSS 전처리기 중 하나, 확장된 CSS 문법을 사용하여 CSS 코드 작성
  • CSS Module : 스타일 작성 시, CSS 클래스가 다른 CSS 클래스의 이름과 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션
  • styled-components : 스타일을 자바스크립트 파일에 내장시키는 방식, 해당 스타일이 적용된 컴포넌트를 제작할 수 있음

 

9.1 가장 흔한 방식, 일반 CSS

가장 중요한 것은 CSS 클래스를 중복되지 않게 만드는 것입니다. 이를 위한 방식은 2가지가 있는데, 하단에서 다루도록 하겠습니다.

 

1. 이름 짓는 규칙

자동 생성된 css에서는 클래스 이름이 컴포넌트 이름-클래스 형태로 지어져 있습니다. (ex. App-logo)

비슷한 방식으로는 BEM 네이밍 방식이 있습니다. 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성합니다. (ex. .card__title-primary)

 

2. CSS Selector

CSS Selector를 사용하면 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일 적용이 가능합니다. 예를 들어, .App 안의 .title에 스타일을 적용하고 싶다면 다음과 같이 작성합니다.

.App .title {
	color: #61dafb;
	animation: App-logo-spin infinite 20s linear;
	height: 40vmin;
}

 

 

9.2 Sass 사용하기

Sass(Syntactically Awesome Style Sheets : 문법적으로 매우 멋진 스타일시트)는 CSS 전처리기입니다.

복잡한 작업을 쉽게 할 수 있도록 하고, 스타일 코드의 재활용성을 높여 주고, 코드의 가독성을 높여 유지보수를 더욱 쉽게 해 줍니다.

구버전의 경우 Sass 사용 시 추가 작업이 필요했지만, v2 버전 이후로는 별도의 작업 없이 Sass를 사용할 수 있습니다.

Sass에서는 두 가지 확장자를 지원합니다.

  • .sass : 초기 Sass는 sass 확장자만 지원했습니다. 중괄호와 세미콜론을 사용하지 않습니다.
$font-stack: Helvetica, sans-serif
$primary-color : #333;

body
	font: 100% $font-stack
	color: $primary-color
  • .scss : 개발자들의 요청으로 지원하게 되었습니다. 기존 CSS 작성 방식과 문법이 비슷합니다.
$font-stack: Helvetica, sans-serif;
$primary-color : #333;

body{
	font: 100% $font-stack;
	color: $primary-color;
}

 

  • node-sass 라이브러리는 Sass를 CSS로 변환해 줍니다.

 

1. utils 함수 분리하기

여러 파일에서 사용할 수 있는 Sass 변수 및 믹스인은 분리하여 작성한 뒤, 필요할 때마다 @import 구문을 이용하여 사용할 수 있습니다. 파일 가장 첫 줄에 @import './styles/utils'를 추가하면 됩니다. 

 

2. sass-loader 설정 커스터마이징하기

Sass 사용 시 필수 작업은 아니지만, 작업해 두면 유용히 사용할 수 있습니다. 

프로젝트에서 디렉터리를 많이 만들어서 구조가 깊어졌을 때, 해당 파일에서는 다음과 같이 상위 폴더로 한참을 거슬러 올라가야 한다는 단점이 있습니다. (ex. @import '../../../styles/utils';)

이를 해결하기 위해서 sass-loader의 설정을 커스터마이징하여 해결할 수 있습니다. 

커스터마이징하기 위해서는 프로젝트 디렉터리에서 yearn eject 명령어를 사용하여 세부 설정으로 밖으로 꺼내 주어야 합니다.

$ yearn eject

$ react-scripts eject

명령어를 사용하면 config라는 디렉터리가 생성됩니다. 프로젝트 디렉터리 > config 디렉터리 > webpack.config.js를 열어 주세요.

use: 에 있는 'sass-loader' 부분을 삭제하고, 뒷부분에 concat을 이용하여 커스터마이징한 sass-loader 설정을 넣어 주세요.

{
	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,
			}
		}),**
	sideEffects:true,
},

설정 파일을 저장한 후, 서버를 재시작하면 utils.scss 파일을 불러올 때 어디에 위치하든 styles 디렉터리 기준 절대 경로를 사용하여 불러올 수 있습니다.

SassComponent.scss 파일에서 import 구문을 @import 'utils.scss'; 로 수정하면 앞으로 utils.scss를 사용하는 컴포넌트를 편하게 사용할 수 있습니다.

매번 utils.scss를 포함시키는 것도 귀찮다면, 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,
			> prependData : `@import 'utils';` <
			}
		}),
	sideEffects:true,
},

이후 utils.scss가 자동으로 불러와지는 걸 확인할 수 있습니다.

 

3. node_modules에서 라이브러리 불러오기

Sass의 장점 중 하나는 라이브러리를 쉽게 불러올 수 있다는 것입니다. yarn을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법은 @import '../../../node_modules/library/styles'; 를 이용하여 불러오는 방법입니다.

../를 반복적으로 작성하는 것이 번거로우므로, 위 문장을 간단하게 바꿔 보겠습니다.

@import '~library/styles'; 다음과 같이 물결 문자, ~를 사용하여 불러오는 방법입니다.

물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 불러올 수 있습니다.

 

다음은 유용한 Sass 라이브러리 두 가지입니다.

 

open-color

Open color scheme for web UI

www.npmjs.com

 

9.3 CSS Module

CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값인, [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어, 클래스 이름이 중복되는 현상을 방지해 줍니다.

이때, 만들어진 클래스는 우리가 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동합니다. (ex. import styles from './CSSModule.module.css'; 등)

구버전, v1에서는 웹팩에서 css-loader 설정을 별도로 해 주어야 하지만, v2 버전 이상부터는 별도의 설정 없이 .moudle.css 확장자로 파일을 저장하기만 하면 적용됩니다.

만약 특정 클래스가 전역적으로 사용되는 경우라면 :global을 앞에 입력해, 전역 CSS임을 명시해 줄 수 있습니다.

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에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있습니다.

고유한 클래스 이름을 사용하려면 클래스를 적용하고 싶은 JSX 엘리먼트에 className={styles.[클래스 이름]} 형태로 전달해 주면 됩니다.

:global을 사용하여 전역적으로 선언한 클래스의 경우, 그냥 문자열로 넣어 주면 됩니다.

 

ES6 문법 템플릿 리터럴을 사용하여 문자열을 합해 주면, 문자열 안에 자바스크립트 레퍼런스를 쉽게 넣어줄 수 있습니다. 키보드에서 숫자 1 옆에 있는 ` 문자, 백틱을 사용해 줍니다.

const name = '리액트'; const message = `제 이름은 ${name}입니다.`;

그러나 템플릿 리터럴을 사용하고 싶지 않다면 다음과 같이 작성해 주면 됩니다.

className = {[styles.wrapper, styles.inverted].join(' ')}

 

1. classnames

classnames는 CSS 클래스를 조건부로 설정할 때 유용한 라이브러리로, 여러 클래스를 적용할 때 매우 편리합니다.

라이브러리 설치는 $ yarn add classnames로 진행합니다.

import classNames from 'clasnames';

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 클래스를 설정할 수 있어, 컴포넌트에서 조건부로 클래스를 설정할 때 매우 편리합니다. 

CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다는 장점이 있습니다. classnames에 내장되어있는 bind 함수를 사용하면 styles.[클래스 이름] 을 사용할 필요 없이, cx('클래스 이름') 형태로 사용할 수 있습니다.

 

2. Sass와 함께 사용하기

Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해 주면 CSS Modules로 사용할 수 있습니다.

 

3. CSS Module이 아닌 파일에서 CSS Module 사용하기

CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용한 것처럼, .css/.scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있습니다.

 

9.4 styled-components

CSS-in-JS : 자바스크립트 파일 안에 스타일을 선언하는 방식으로, 이와 관련된 라이브러리는 https://github.com/MicheleBertoli/css-in-js 에서 확인할 수 있습니다.

그 중 개발자들이 가장 선호하는 라이브러리는 styled-components 방식입니다.

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`
bakcground: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;

 

1. Tagged 템플릿 리터럴

스타일을 작성할 때 `을 사용하여 만든 문자열에, 스타일 정보를 넣어 주는 방식입니다. 일반 템플릿과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때, 온전히 추출할 수 있다는 점입니다.

  • 템플릿에 객체나 함수를 넣으면 객체는 "[object Object]"로, 함수는 함수 내용이 문자열화로 본래의 형태를 잃어버리게 됩니다.

그러나 Tagged 템플릿을 사용하면 위와 같은 현상을 방지할 수 있습니다.

function tagged(...args) {
	console.log(args);
}
tagged`hello ${{foo:'bar'}} ${() => 'world'}!`

 

2. 스타일링 된 엘리먼트 만들기

styled-components를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현합니다.

import styled from 'styled-components';
const MyComponent = styled.div`
	font-size: 2rem;
`;

사용해야 하는 태그명이 유동적이거나, 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현해 줍니다.

//태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
	background: gray;
`
//아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)`
	color:blue;
`

 

3. 스타일에서 props 조회하기

styled-components를 사용하면 스타일쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있습니다.

const Box = styled.div`
	background:${props => props.color || 'blue'};
	padding:1rem;
	display:flex;
`;

위와 같이 만들어진 코드는 JSX에서 사용할 때 color 값을 props로 넣어 줄 수 있습니다. (ex. <Box color="black">(...)</Box>)

 

4. props에 따른 조건부 스타일링

일반 CSS 클래스를 사용하여 조건부 스타일링을 할 때는 className을 사용했지만, styled-components에서는 props로도 간단하게 처리할 수 있습니다.

/* StyledComponents.js - Button 컴포넌트 */

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>

 

5. 반응형 디자인

브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 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%;
	}
`;

일반 CSS에서 할 때와는 큰 차이가 없지만, 여러 컴포넌트에서 반복해야 한다면 귀찮을 수 있습니다. 그럴 땐 이 작업을 함수화하여 간편하게 사용할 수 있습니다.

import React from 'react';
import styled, {css} from 'styled-components';

const sizes = {
	desktop:1024,
	tablet:768
};

// 상단의 size 객체에 따라 자동으로 media 쿼리 함수를 만들어 줍니다.
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`
	background: ${props => props.color || 'blue'};
	padding: 1rem;
	display:flex;

	width:1024px;
	margin:0 auto;
	${media.desktop`width: 768px;`}
	${media.tablet`width:100%;`};

media를 선언하고 여기저기에서 불러와 사용하는 방식이 훨씬 편리할 것입니다.

 

Quiz

1. Sass는 ____ 과 ____, 2가지 확장자를 지원합니다. 이 둘의 차이는 ____ 와 _____ 를 사용하느냐 마느냐의 차이입니다.

2. 파일을 임포트할 때 _____ 를 사용하면 자동으로 node_modules에서 라이브러리를 탐지해 줍니다.

3. CSS Module을 적용하기 위해서는 파일 확장자를 ________ 로 저장해 줍니다.

4. ________를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 상단에서 styled를 불러오고, _______을 사용하여 구현합니다.

 

더보기
더보기

1. scss, sass, 중괄호, 세미콜론

2. 물결 문자, ~

3. .module.css

4. styled-components, styled.태그명


Corner React Starter #1
Editor PeeP

728x90

관련글 더보기