[스터디]리액트를 다루는 기술-컴포넌트 스타일링

스타일링 방식

  • 일반 CSS : 컴포넌트를 스타일링 하는 기본적인 방식
  • Sass : CSS 전처리기 중 하나로 확장된 CSS문법을 사용
  • CSS Module : CSS 클래스의 고유한 이름을 자동으로 생성해주는 옵션
  • styled-components : 스타일을 자바스크립트에 내장시키는 방식

일반 CSS

  • 이름 짓는 규칙 : “컴포넌트 이름”-“클래스” 형태로 생성
// App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  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 {
  height: 40vmin;
  pointer-events: none;
  animation: App-logo-spin infinite 20s linear;
}

/* App안에 있는 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);
  }
}

Sass 사용하기

Sass(Synatically Awesome Style Sheets, 문법적으로 매우 놀라운 스타일시트) : CSS 전처리기로 복잡한 작업을 쉽게 해주고, 스타일 코드의 재활용성, 코드의 가독성을 높여줌

// 설치
$ yarn add node-sass
// 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;
    }
  }
}

// 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 './App.css';
import SassComponent from "./SassComponent";

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


export default App;

자주 사용하는 믹스인(재사용가능한 스타일 블록)을 utils.scss로 따로 분리한 모습

// ./styles/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';

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

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

프로젝트의 디렉토리 깊이가 깊어져, 필요한 파일을 @import 하기 쉽지 않을 때, sass-loader의 설정을 커스터마징하여 해결 할 수 있다. 다만, create-react-app으로 만들어진 프로젝트는 기본적으로 세부설정이 숨겨져 있어 이를 꺼내기 위한 작업이 아래와 같이 필요한다. 단, 사전에 commit 모두 되어 있어야 한다.

$ yarn eject
yarn run v1.21.1
$ react-scripts eject
NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: https://reactjs.org/blog/2018/10/01/create-react-app-v2.html

? Are you sure you want to eject? This action is permanent. (y/N)

위 명령이 잘 실행되면 config 디렉토리가 생성되며, webpack.config.js 를 아래와 같이 수정한 뒤, 서버를 재시작한다. 만약 서버가 재시작 되지 않으면, node_modules 디렉토리를 삭제 후 $yarn install 하여 모듈을 재설치한다.

            // Opt-in support for SASS (using .scss or .sass extensions).
            // By default we support SASS Modules with the
            // extensions .module.scss or .module.sass
            // src/styles 기준 절대경로를 사용하여 필요한 파일을 임포트한다.
            // 교재의 옛 버전에서 설정은 오류가 발생하니 아래와 같이 수정하도록하자.
            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders(
                {
                  importLoaders: 3,
                  sourceMap: isEnvProduction && shouldUseSourceMap,
                }).concat({
                loader: require.resolve('sass-loader'),
                options: {
                  // prependData:`@import 'utils';`,
                  sassOptions: {
                    includePaths: [paths.appSrc + '/styles'],
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                  }
                }
              }),

상단 prependData:@import 'utils'; 의 주석을 제거하게 되면, .scss 파일을 불러올 때마다, @import ‘utils’가 자동으로 수행된다. 모든 파일에 추가할 때 사용하면 편리한 기능이다.

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

우선 학습을 위해 2가지 라이브러리를 아래와 같이 설치한다.

$ yarn add open-color include-media

utils.scss 에 아래와 같이 라이브러리를 넣어 사용한다. import 할 때, node_module 경로는 ~ 으로 치환할 수 있다.

// 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
.SassComponent {
  display: flex;
  background: $oc-gray-2;
  @include media('<768px') {   // 가로크기가 768px 이하이면 배경을 검정색으로 바꿔준다.
    background: $oc-gray-9;
  }
  .box {  // 일반 CSS 에선 .SassComponent .box 와 마찬가지
    background: $oc-gray-2;
    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;
    }
  }
}

CSS Module

CSS Module은 CSS를 불러와 사용할 때, 클래스 이름을 고유한 값(파일이름_클래스이름_해시값)으로 자동으로 만들어준다.

.module.css 확장자로 파일을 저장하면, 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 React from "react";
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;
// App.js
import React, {Component} from 'react';
import './App.css';
import CSSModule from "./CSSModule";

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

export default App;

classnames

CSS 클래스를 조건부로 설정할 때 유용한 라이브러리

$ 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'
// CSSModule.js 
import React from "react";
import classNames from 'classnames/bind';
import styles from './CSSModule.module.css';

const cx = classNames.bind(styles);


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

Sass와 CSSModule 함께 사용하기

// CSSModule.module.scss
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
    &.inverted {
        // invert가 .wrapper과 같이 사용될 때만 적용
        color: black;
        background: white;
        border: 1px solid black;
    }
}

/* 글로벌 CSS 를 작성하고 싶다면 */
:global {
    .something {
        font-weight: 800;
        color: aqua;
    }
}

styled-components

자바스크립트 파일 안에 스타일을 선언하는 방식. ‘CSS-in-JS’라고 부른다. styled-components, emotion 등의 라이브러리가 많이 쓰인다. 여기서는 styled-components를 알아본다.

$ yarn add styled-components
// StyledComponents.js
import React from 'react';
import styled, { css } from 'styled-components';  // 여러줄의 스타일 구문을 조건부로 설정해야 하는 경우 css를 불러와야함

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

export default StyledComponent;

intellij에서 위 코드가 하이라이팅이 제대로 되지 않는 경우 플러그인 “Styled Components & Styled JSX”을 설치하면 제대로 나온다.

// App.js
import React, {Component} from 'react';
import './App.css';
import StyledComponent from "./StyledComponents";

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

export default App;

Tagged 템플릿 리터럴

스타일을 작성할 때 `을 사용하여 만들었는 데, 이를 Tagged 템플릿 리터럴이라고 한다. 이 것은 일반 템플릿 리터럴과 달리 템플릿 안에 자바스크립트 객체나 함수를 온전히 추출할 수 있다는 점이다.

// 브라우저에서 테스트 해본다.
`hello ${{foo: 'bar'}} ${() => 'world'}!`
=> "hello [object Object] () => 'world'!"

function tagged(...args) {
    console.log(args);
}
tagged`hello ${{foo: 'bar'}} ${() => 'world'}!`
=> (3) [Array(3), {…}, ƒ]
0: (3) ["hello ", " ", "!", raw: Array(3)]
1: {foo: "bar"}
2: () => 'world'

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

import styled from 'styled-components';

// 폰트 크기가 2rem인 div가 생성된다.
const Mycomponent = stled.div`
    font-size: 2rem;
`;

// 태그 타입을 styled 함수 인자로 전달
const MyInput = styled('input')`
    background : gray;
`

// 컴포넌트 자체를 전달
const StyledLink = styled(Link)`
   color : blue;
`

반응형 디자인

// StyledComponent.js
import React from 'react';
import styled, { css } from 'styled-components';

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

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;

You may also like...

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다