본문 바로가기

IT/React

[Leaning-React] 09. 컴포넌트 스타일링

반응형

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

 

src 폴더에 위치한 App.js, App.css 파일을 통해 스타일을 적용할 수 있다.

CSS 클래스의 이름은 중복되서는 안된다. 그럼으로 컴포넌트이름-클래스형태로 (App-header) 작성하는 것을 권장한다.

 

CSS Selector를 통해 CSS 클래스가 특정 클래스 내부에서만 적용되게 할 수 있다.

 

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
    <div className="App">
        <header>
            <img src={logo} className="logo" alt="logo" />
            <p> Edit <code>src/App.js</code> and save to reload. </p>
            <a href="https://reactjs.org" target="_blank" rel="noopener noreferrer"> Learn React </a>
        </header>
    </div>
    );
  }
}

export default App;

 

App.css

.App {
  text-align: center;
}

/*.App 안에 들어있는 .logo*/
.App .logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
}

/* .App 안에 들어있는 header 
   header 클래스가 아닌 header 태그 자체에 
   스타일을 적용하기 때문에 . 이 생략되었습니다. */
.App header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

/* .App 안에 들어있는 a 태그 */
.App a {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

 

.App .logo { ~} 와 같이 CSS 클래스에 작성한다면 App.js 의 ClassName이 logo인 컴포넌트에만 해당 스타일이 적용된다.

 

즉, 앞단에 클래스 이름 .App를 넣고, .logo 또는 header 와 같은 태그를 작성하여 지정할 수 있다.

 

2. Sass 사용하기

 

Sass는 CSS 전처리기로 복작한 작업을 쉽게 해주며, 스타일 코드의 재활용성과 가독성을 높여준다.

Sass의 확장자는 .scss와 .sass가 있으나 .scss 확장자는 기존 CSS 작성방식과 비슷함으로 더 자주 사용된다.

 

node-sass 라이브러리(Sass를 CSS로 변환해주는 라이브러리)를 설치한다. 

> $yarn add node-sass

 

src 디렉토리에 

 

SassComponent.scss

//변수사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c05;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;

//믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@mixin square($size) {
    $calculated: 32px * $size;
    width: $calculated;
    height: $calculated;
}

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

 

SassComponent.js

import React from 'react';
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;

App.js

import React, { Component } from 'react';
import SassComponent from './SassComponent';

class App extends Component {
  render() {
    return (
    <div>
        <SassComponent></SassComponent>
    </div>
    );
  }
}

export default App;

 

2-1) utils 함수 분리하기

Sass  변수와 믹스인은 다른 파일로 분리시킬 수 있다. 

 

src 디렉토리에 styles라는 디렉터리를 생성하고 그안에 utils.scss 파일을 만들어 분리시켜보자.

 

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를 사용해서 다른 scss를 불러올 수 있다.

> @import './styles/utils';

 

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

먼저, yarn eject 명령어를 통해 세부설정을 밖으로 꺼내야 된다. 그 후 생성된 config 디렉터리 내 존재하는 

webpack.config.js 를 열어본다.

 

해당 파일에서 sassRegex를 찾아 use: 에 있는 sass-loader 부분을 지우고 concat을 통해 커스터마이징 된 

sass-loader 설정을 넣어주면 된다.

 

더보기

<변경되는 부분>

.concat({
   loader: require.resolve('sass-loader'),
   options: {
      includePaths: [paths.appSrc + '/styles'],
      sourceMap: isEnvProduction && shouldUseSourceMap,
      data: `@import 'utils';`
   }
})

이를 통해 scss 파일의 경로를 매번 다 작성할 필요없이 styles 디렉토리 기준으로 절대 경로를 사용할 수 있다.

> @import './styles/utils'; => @import 'utils.scss'; 로 변경해서 작성 가능

 

또한, sass-loader의 옵션에 data 필드에 매번 추가되는 코드를 작성함으로써 sass 파일을 불러올때마다

해당 코드가  자동으로 맨 윗부분에 포함시킬 수 있다.

 

위와 같은 설정들은 sass 사용시 반드시는 아니지만, 해 두면 유용하다.

 

2-3) node_modules에서 라이브러리 불러오기

물결 문자(~)을 사용하는 방법으로 자동으로 node_modules에서 라이브러리 디렉토리를 탐지해 스타일을 

불러올 수 있다.

 

3. CSS Module

CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값([파일이름]_[클래스이름]_[해시값])을 자동으로

만들어 중첩되는 현상을 방지하는 기술이다.

 

v2 버전이상부턴 별도의 설정없이 .module.css 확장자로 파일을 저장하기마 하면 CSS Module이 적용된다.

 

src 디렉토리에 

CSSModule.module.css

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

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

/* 글로벌 CSS 를 작성하고 싶다면 */
:global {
  // :global {} 로 감싸기
  .something {
    font-weight: 800;
    color: aqua;
  }
  // 여기에 다른 클래스를 만들 수도 있겠죠?
}

 

src 디렉토리에 CSS Module이 사용될 컴포넌트를 작성해보자

CSSModule.js

import React from 'react';
import styles from './CSSModule.module.scss';

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

export default CSSModule;

일반 클래스 이름을 사용할 경우엔 className = {styles.[클래스 이름]}으로 작성하고,

전역으로 선언한 클래스를 사용할 경우엔 calssName = "[클래스 이름]"으로 작성하면 된다.

 

App.js 

import React, { Component } from 'react';
import CSSModule from './CSSModule';

class App extends Component {
  render() {
    return (
    <div>
        <CSSModule/>
    </div>

    );
  }
}

export default App;

 

CSS Module 를 두개 이상 적용하고 싶을때 

 

먼저, CSSModule.module.css 에 아래 코드를 추가한다.

.inverted {
    // inverted 가 .wrapper 와 함께 사용 됐을 때만 적용
  color: black;
  background: white;
  border: 1px solid black;
}

 

CSSModule.js 를 아래와 같이 작성한다면 두개의 CSS Module을 적용할 수 있다.

import React from 'react';
import styles from './CSSModule.module.scss';

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

export default CSSModule;

 

3-1) classnames 

classnames은 css클래스를 조건부로 설정할 때 유용한 라이브러리이다.

 

먼저, classnames 라이브러리를 설치하자.

> yarn add classnames

 

classnames 사용법

1) classNames('one', 'two'); 

=> "one two"

2) classNames('one', { two: true});

=> "one two"

3) classNames('one', { two: false});

=> "one"

4) classNames('one', ['two', 'three'])

=> "one two three"

5) const myClass = 'hello'; classNames('one', myclass, { myCondition: true});

=> "one hello myCondition"

 

/* classNames 라이브러리는 사용한 경우 */
const MyComponent = ({ highlighted, theme}) => (
	<div className={classNames('MyComponent', {highlighted}, theme)}>
    	Hello
    </div>	
)

/* classNames 라이브러리는 사용 안하는 경우 */
const MyComponent = ({ highlighted, theme}) => (
	<div className={'MyComponent ${theme} ${highlighted ? 'highligted': ''}'}>
    	Hello
    </div>	
)

classNames을 사용함으로써 가독성을 높일 수 있다.

 

또한, CSS Module 과 함께 사용한다면 styles.[클래스이름] 형태 대신에 cx('클래스 이름', '클래스이름 2') 형태로 사용할 수 있다.

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

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

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

 

3-2) Sass와 함께 사용하기

Sass 파일 뒤에 .module.scss 확장자를 사용해 주면 CSS Module로 사용할 수 있다.

 

CSSModule.module.scss

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

.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;
  }
  // 여기에 다른 클래스를 만들 수도 있겠죠?
}

 

CSSModule.js 상단에서 css 대신에 scss로 변경해주면 된다.

 

4. Styled-components

자바스크립트 파일 안에 스타일을 선언하는 방식 (CSS-in-JS)

 

Styled-components 라이브러리를 설치해보자

> yarn add Styled-components

 

src 디렉토리 안에 

StyleComponent.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;
  width: 1024px;
`;

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 StyledComponent = () => (
  <Box color="black">
    <Button>안녕하세요</Button>
    <Button inverted={true}>테두리만</Button>
  </Box>
);

export default StyledComponent;

 

App.js

import React, { Component } from 'react';
import StyledComponent from './StyledComponent';

class App extends Component {
  render() {
    return (
      <div>
        <StyledComponent />
      </div>
    );
  }
}

export default App;

 

props 값을 전달해주는 값을 쉽게 스타일에 적용할 수 있는 점이 장점이다.

 

4-1) Tagged 템플릿 리터럴

'을 사용해서 스타일 정보를 넣어주는 문법을 Tagged 템플릿 리터럴이라고 부른다.

Tagged 템플릿 리터럴을 사용해서 자바 스크립트 객체나 함수의 원본 값 그대로 추출할 수 있어 Styled-componenets에 props를 쉽게 적용할 수 있다. 

 

크롬개발자 도구에서  아래의 코드를 비교하여 내용을 확인 할 수 있다.

1번 
'hello ${{foo: 'bar'}} ${() => 'world'}!'

=> 결과 "hello [object object] () => 'world' !"

2번
function tagged(...args) {
	console.log(args);
}

tagged'hello ${{foo: 'bar'}} ${() => 'world'}!'

 

4-2) 스타일링된 엘리먼트 만들기..... 설명 필요...

import React from 'react';

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

 

4-3) 스타일에서 props 조회

props를 조회하여 color 값이 없으면 기본값을 blue로 설정할 수 있다.

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  ';
  
 <Box color="black">(...)</Box>

 

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

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

const Button = styled.button`
  ...

  /* 다음 코드는 inverted 값이 true 일 때 특정 스타일을 부여해줍니다. */
  ${props =>
    props.inverted &&
    css`
      background: none;
      border: 2px solid white;
      color: white;
      &:hover {
        background: white;
        color: black;
      }
    `};
    ...
`;

 

조건부 스타일링을 할때 여러 줄의 코드에서 props를 참조한다면 반드시 css로 감싸서 Tagged 템플릿 리터럴을 사용해야 된다.

<Button inverted={true}>테두리만</Button>과 같이 작성함으로써 해당 css를 적용할 수 있다.

 

4-5) 반응형 디자인

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

일반 CSS와 동일하게 media 쿼리를 사용하여 브라우저의 가로 크기에 따라 다른 스타일을 적용할 수 있다.

동일한 작업이 여러 번 발생할 경우 아래와 같이 함수화하여 간편하게 사용할 수 있다.

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

 

 

 

참고서적 : 리엑트를 다루는 기술 (김민준)

반응형

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

[Leaning-React] 10. 일정 관리 앱 애플리케이션 만들기  (1) 2020.01.30