티스토리 뷰

WEB/React

[React] 컴포넌트 스타일링

춘햄 2023. 3. 27. 19:30

리액트에서 컴포넌트를 스타일링할 때 다양한 방식을 사용할 수 있다. 여러 방식 중에서 딱히 정해진 방식이란 없지만, 보통 개발할 프로젝트의 특징이나 개발자의 취향으로 갈린다.

 

이번에 다뤄볼 리액트 스타일링 방식은 다음과 같다.

 

1. 일반 css: 컴포넌트를 스타일링하는 가장 기본적인 방식

2. Sass: 자주 사용되는 css 전처리기 중 하나로 확장된 css 문법을 사용하여 css 코드를 더욱 쉽게 작성할 수 있도록 해준다.

3. css Module: 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해주는 옵션이다.

4. styled-components: 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 해준다.


1. 가장 흔한 방식, 일반 CSS

기본적으로 리액트 프로젝트를 생성하면 일반 CSS 방식으로 만들어진다. 기존의 CSS 스타일링이 딱히 불편하지 않고 새로운 기술을 배울 필요가 없다고 생각되면, 일반 CSS를 계속 사용해도 무방하다.

 

기본적인 CSS 선택자 등과 같은 개념은 이미 많이 다룬 내용이기 때문에 따로 다루지는 않으려고 한다.


2. Sass 사용하기

Sass는 Syntactically Awesome Style Sheets의 약자로 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해준다. 

 

리액트 구버전은 Sass를 사용하려면 별도의 추가 작업이 필요했는데, v2 버전부터는 별도의 추가 설정 없이 바로 사용할 수 있다.

 

Sass에서는 두 가지 확장자 .scss와 .sass를 지원한다. 

 

각 확장자의 문법은 다음과 같다.

 

◎.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;
}

 

보통 scss문법이 더 자주 사용되므로, scss로 예제를 작성해보자.

 

우선, 터미널에 npm install sass 입력하여 모듈을 설치해줘야 한다.

 

이제 scss 파일을 작성해보자.

 

◎SassComponent.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 {
  display: flex;
  .box { // 일반 css에서는 .$SassComponent .box와 마찬가지
    background: red;
    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;
    }
  }
}

그러고 나서 scss를 임포트할 js파일도 아래와 같이 작성해주면 된다.

 

◎SassComponent.js

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;

 


utils 함수 분리하기

여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있다.

 

◎utils.cscc

// 변수 사용하기
$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 './utils';

.SassComponent {
  display: flex;
  .box { // 일반 css에서는 .$SassComponent .box와 마찬가지
    background: red;
    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;
    }
  }
}

node_modules에서 라이브러리를 불러오기 

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

 

우선 npm install open-color include-media를 입력하여 유용한 Sass 라이브러리인 open-color와 include-midia를 설치하자.

 

이제 utils.scss에

@import '~include-media/dist/include-media';
@import '~open-color/open-color';

와 같이 임포트해주고, 사용하기만 하면 된다.

 

◎SassComponent.scss

@import './utils';

.SassComponent {
  display: flex;
  background: $oc-gray-2;
  @include media('<768px') {
    background: $oc-gray-9;
  }

  	...
  
  }
}


3. CSS Module

CSS Module은 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;
}

CSS Module을 사용하면 클래스 이름을 지을 때 그 고유성에 대하여 고민하지 않아도 된다. 흔히 사용하는 단어로 이름을 짓는다고 해도 전혀 문제가 되지 않는다. 

 

◎ 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;

개발자 콘솔로 확인해보면, 해당 클래스가 고유 값인 CSSModule_wrapper_-Zsq3로 지정이 되어 있는 걸 확인할 수 있다.

 

혹은 CSS Module을 사용한 클래스 이름을 두 개 이상 적용하려면 아래와 같이 템플리 리터럴 문법을 사용할 수 있다.

 

◎ CSSModule.module.css

/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */

.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
}

.inverted {
    color: black;
    background: white;
    border: 1px solid black;
}

/* 글로벌 CSS를 작성하고 싶다면 */
:global .something {
    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;

Templete Literal은 숫자 1키 왼쪽에 있는 ` (Backtick)을 사용하여 작성하여 문자열 안에 자바스크립트 레퍼런스를 추가하는 문법이다.


classnames 라이브러리를 사용하여 CSS 클래스를 조건부로 설정할 수 있다. 또한 CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리하다.

 

우선, npm install 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이면 highlighted 클래스가 적용되고, false면 적용되지 않는다. 추가로 theme으로 전달받는 문자열 내용 그대로 클래스에 적용된다.

 

즉, classnames를 사용하지 않는다면 아래와 같이 짜야 한다는 것이다.

const MyComponenet = ({highlighted, theme}) => (
    <div className={`MyCoponent ${theme} ${highlighted ? 'highlighted' : ''}`}>
        Hello
    </div>
)

 

이제, 위 예제에 적용해보면

 

◎ CSSModule.js

import styles from './CSSModule.module.css';
import classNames from 'classnames/bind';

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를 사용하는 것이 훨씬 가독성이 높다.


Sass와 함께 사용하기

Sass를 사용할 때도 파일 이름 뒤에 module.scss 확장자를 사용하면 CSS Module을 사용할 수 있다.

 

◎CSSModule.module.scss

.wrapper {
  background: black;
  padding: 1rem;
  color: white;
  font-size: 2rem;
  &.inverted {
    color: black;
    background: white;
    border: 1px solid black;
  }
}

:global {
  .something {
    font-weight: 800;
    color: aqua;
  }
}

 

◎ CSSModule.js

import styles from './CSSModule.module.scss';
import classNames from 'classnames/bind';

const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아오도록 설정

const CSSModule = () => {
    return (
        <div className= {cx('wrapper', 'inverted')}>
            안녕하세요, 저는 <span className="something">CSS Module</span>
        </div>
    );
};

export default CSSModule;

 

또한 CSS Module이 아닌 파일에서 CSS Module을 사용할 수도 있는데, 아래와 같이 :local을 사용하면 된다.

:local .wrapper {
	...
}

:local {
	.wrapper {
		...
    }
}

4. Styled-components

컴포넌트 스탕일링의 또 다른 패더다임은 자바스크립트 파일 안에 스타일을 선언하는 방식이다. 이 방식을 'CSS-in-JS'라고 부른다. 이와 관련된 라이브러리는 정말 많다. 

 

여기서는 CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components 알아보려고 한다.

 

npm install styled-components

 

로 우선 패키지를 설치하자.

 

이 라이브러리를 통해 예제 컴포넌트를 만들어보자. sytled-components를 사용하여 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자르 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.

 

예제 코드를 살펴보자.

 

◎SytledComponent.js

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: center;
    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;

 

◎App.js

import StyledComponent from "./SytledComponent";

const App = () => {
        return (
            <>
              <StyledComponent/>
            </>
        );
};

export default App;

styled-components와 일반 classNames를 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해주는 값을 쉽게 스타일에 적용할 수 있다는 점이다. 

 

좀 더 자세히 알아보자.


Tagged 템플릿 리터럴

앞에서 작성한 코드를 확인해보면, 스타일을 작성할 때 `를 사용하여 만든 문자열에 스타일 정보를 넣어준다. 여기서 사용한 문법을 Tagged 템플릿 리터럴이라고 부른다. CSS Module을 배울 때 나온 일반 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다는 점이다. 

 

이는 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해준다.


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

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 컴포넌트는 나중에 리액트 라우터를 배울 때 사용할 컴포넌트이므로 이런 게 있구나~ 정도로만 알아두고 일단 넘어가자.


스타일에서 props 조회하기

styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 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를 사용하여 조건부 스타일링도 구현했다.

const Button = styled.button `
    background: white;
    color: black;
    border-radius: 4px;
    padding: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: center;
    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>

반응형 디자인

이번에는 styled-components를 사용할 때 반응형 디자인을 어떻게 하는지 한번 알아보자.

 

브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media 쿼리를 사용하면 된다. 조금 전 작성한 Box 컴포넌트를 아래와 같이 수정해보자.

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%;
    }
`;

리액트에서 Style을 적용하는 대표적인 몇 가지 방법을 알아보았는데, 내용이 좀 많다...

 

이를 모두 암기하고 있을 필요는 없고, 취향과 프로젝트에 맞는 방법을 하나 선택하여 사용하면 된다.

 

 

 

끝!

반응형

'WEB > React' 카테고리의 다른 글

[React] Todo List: 컴포넌트 성능 최적화  (0) 2023.03.29
[React] First Project: Todo List  (0) 2023.03.28
[React] Hooks  (0) 2023.03.27
[React] 컴포넌트의 라이프사이클  (0) 2023.03.24
[React] 컴포넌트 반복  (0) 2023.03.24
Comments