본문 바로가기

React

20210607 React05 : Style Loders, CSS, SASS, CSS module, SASS module, classNames 라이브러리(객체 key module.css로 만들기), 객체 value 생략표현

React 05



React Component Styling


Style Loders


  • Style을 적용하기 위한 파일들이 동작하기 위함 배포용 파일로 변화시키기 위한 설정들로 4가지 방식이 있음
    • CSS, CSS Module, Sass, Sass Module
    • 위의 4개의 type에 webpack에서 loader를 붙이고 설정하여 사용하게 됨
// webpack.config.js
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;



CSS, SASS


  • 기본적으로 react의 스타일링 체계의 경우 컴포넌트에 귀속적으로 스타일링이 되는 것이 아니고 전역적으로 스타일링을 필요로 함
  • react가 다른 css체계에 의존하지 않고 독립적인 스타일링 하는 기능을 제공하고 있지 않음 (컴포넌트 별로 스코핑이 되지 않음)
  • head에 style 태그에 순서대로 쌓이는 구조임

CSS Styling


  • 스타일을 줄려고 하는 class의 이름들이 전역적으로 오염되는 것을 주의해야 함
    • 방법01 : 이런 오염들을 막기 위해 CSS 방법론(Naming)이 생겨남 -> BEM (Block, Element, Modifier)
      • CRA 기본 app.css 의 className은 BEM으로 기본설정 되어 있음
    • 방법02 : 물론, css에서 제공하고 있는 위계질서 (일치, 후손, 자식, 등등..)을 사용하면 됨

SASS Styling


  • css 스타일링의 한계점을 개선하기 위해 다른 문법을 사용해서 css로 변환해서 사용하는 방식이 있음
  • 대표적으로 SCSS인데, 스타일링 문법에 포함 구조를 넣어 위계 질서를 더 편하게 줄 수 있고, 그외의 변수 및 여러 기능들을 제공하여 편함

  • webpack loader에는 SCSS 변환을 연결해 놓았지만, dev server에서는 확인할 수 없기 때문에 따로 SCSS 모듈을 설치해서 확인 해야함 (npm i sass , 패키지 이름이 scss가 아님을 주의!)
  • CRA에서는 scss 설정이 모두 되어 있기 때문에 scss 만 설치하면 쉽고 간편하게 scss 방식을 사용 할 수 있음
// App.css 를 App.scss 형식으로 구조를 바꾸어 설정
.App {
  text-align: center;

  .logo {
    height: 40vmin;
    pointer-events: none;
  }

  @media (prefers-reduced-motion: no-preference) {
    .logo {
      animation: App-logo-spin infinite 20s linear;
    }
  }

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

  .link {
    color: #61dafb;
  }

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

  • App.js에 scss 파일 연결 (물론 sass가 설치된 상황이어야 함)
// App.js
import logo from "./logo.svg";
// import './App.css';
import "./App.scss";

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

export default App;



CSS module, SASS module


CSS, SASS Moudule


  • CRA : CSS Module
  • 기존의 CSS, SASS 파일을 import하는 경우 전역적으로 style이 순서만 다르게 들어가기 때문에 스코프가 오염될 수 있는 단점이 있음
  • CSS Module은 그런 오염을 막을 수 있게 자동으로 클래스 명을 바꾸어 사용할수 있게 도와줌
  • 일반적인 형식의 후손, 일치 선택자 등을 사용한 위계 질서를 통해서 작성된 CSS를 파일명이 module.css로 끝나게 파일을 하나 만들고 사용될 JS파일에서 import 시켜서 styles 객체를 꺼내와 사용하면 됨
  • 이 모듈의 경우 우리가 작성한 className을 [filename]_[classname]__[hash] 형식으로 바꾸어 줌

  • SASS Module도 이미 다 CRA에서 설정이 되어 있기 때문에, 그냥 module.scss로 scss 파일 만들고 사용할 컴포넌트 JSX파일에 import만 시켜주면됨

import styles from "./App.module.css";
console.log(styles);

// {
//  App: "App_App__2asuU"
//  App-logo-spin: "App_App-logo-spin__3qVcD"
//  header: "App_header__3G0EC"
//  link: "App_link__1KD3u"
//  logo: "App_logo__3Ycvw"
// }
  • 범용적인 이름을 css class명으로 사용하여 작성했지만, JSX에서 className 붙일때 module을 통해서 자동으로 hash 값을 부여하도록 하기 때문에 겹치지 않게 class 명을 만들어줌
import styles from "./App.module.css";
function App() {
  return (
    <div className={styles["App"]}>
      <header className={styles["header"]}>
        <img src={logo} className={styles["logo"]} alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className={styles["link"]}
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
  • 모듈에서 자동으로 만들어준 key-value 구조로 JSX에서는 className으로 모듈의 key를 통해 값을 불러오게 연결하여 사용하면 됨
  • styles['key'] 형태로 사용하여 할당



컴포넌트 전용 css, sass moudule 관리


  • 컴포넌트 JSX 파일에 해당 JSX파일의 CSS module 파일을 연결함으로써 전역적인 관리가 아닌 독립적인 관리가 가능해진다.

  • state가 없는 상황, function component 사용시

// 해당 컴포넌트에 맞는 css를 연결
import styles from "./Button.module.css";
const Button = (props) => <button className={styles["button"]} {...props} />;
export default Button;
.button {
  background-color: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
}


  • state를 사용하여, class component 사용시
    • 버튼을 누르면 state의 loading 값이 true 그리고 1초뒤 false로 변함
    • className에 loading 값을 조건으로 삼항 연산자를 통해서 className을 제어함
      • button만 아니면 button, loading 둘다.
import React from "react";
import styles from "./Button.module.css";
class Button extends React.Component {
  state = {
    loading: false,
  };
  render() {
    return (
      <button
        onClick={this.startLoading}
        className={
          this.state.loading
            ? `${styles["button"]} ${styles["loading"]}`
            : styles["button"]
        }
        {...this.props}
      />
    );
  }

  startLoading = () => {
    this.setState({
      loading: true,
    });
    setTimeout(() => {
      this.setState({
        loading: false,
      });
    }, 1000);
  };
}
export default Button;
.button {
  background-color: transparent;
  border-radius: 3px;
  border: 2px solid palevioletred;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  font-size: 20px;
}

.loading {
  border: 2px solid grey;
  color: grey;
}
  • 이렇게 하면 간단한 기능이지만 생각보다 코드가 많이지고 복잡해 져서 비효율적으로 보이기 때문에 라이브러리를 통해서 개선할 수 있다.



ClassNames 라이브러리


  • className을 쉽게 사용할 수 있게 해줌
  • npm i classnames
  • classNames로 함수를 받아 사용함
  • classNames의 경우 인자를 넣으면 해당 인자를 스페이스로 띄워 반환시켜줌
  • true, false값을 가진 key-value의 객체{key: true or false}를 인자로 넣으면 해당 인자의 값이 true 이면, 정상적으로 내보내고, false이면 내보내지 않음
  • 정확히 true, false가 아닌 Falsy, Truthy 값도 인지하여 값을 선택함
  • null, false, undefined, 0, { bar: null }, '' -> Falsy
  • 'bar', 1 -> Truthy
import styles from "./Button.module.css";
import classNames from "classnames";
console.log(classNames("foo", "bar")); // foo bar
console.log(classNames("foo", "bar", "baz")); // foo bar baz
console.log(classNames({ foo: true }, { bar: false })); // foo
console.log(classNames(null, false, "bar", undefined, 0, 1, { bar: null }, "")); // bar 1
console.log(classNames(styles["button"], styles["loading"]));
// Button_button__2Px41 Button_loading__2Fgtc


  • 객체 형태로 값을 가지게 하고 싶은데 key값은 []또는 .의 형태로 지정 못함
import React from "react";
import styles from "./Button.module.css";
import classNames from "classnames";

class Button extends React.Component {
  state = {
    loading: false,
  };
  render() {
    return (
      <button
        onClick={this.startLoading}
        className={classNames(styles["button"], {
          loading: this.state.loading,
          // loading이라는 class이름이 있냐 없냐가 됨 (이 방법으론 module형을 표현 불가함)
          // styles['loading']: this.state.loading 표현 불가
        })}
        {...this.props}
      />
    );
  }

  startLoading = () => {
    this.setState({
      loading: true,
    });
    setTimeout(() => {
      this.setState({
        loading: false,
      });
    }, 1000);
  };
}
export default Button;



classNames 라이브러리 : 객체 key값 module.css 화 시키는 방법


  • 이러한 문제점을 해결하기위해서 classNames 라이브러리에서 제공하고 있는 bind 함수를 사용하면 됨
  • classNames.bind(styles)를 받아 기존의 classNames 함수 사용하는 것 처럼 사용하면됨
import styles from './Button.module.css';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);
console.log(cx('button', 'loading')); // styles 모듈을 거친 표현으로 className이 바뀜

render() {
  return (
    <button
      onClick={this.startLoading}
      className={cx('button', {loading: this.state.loading})}
      {...this.props}
    />
  );
}



객체 생략 표현 사용하여 더 간단하게 만들기


  • 미리 state 값을 객체 key와 같은 이름의 변수로 받아와 사용하여 연결하면, 객체의 key-value 생략 표현을 사용할 수 있음
  • 객체의 key이름과 값의 변수 명이 같으면 생략 표현 가능
  • {keyName : valueVariableName} -> keyName === valueVariableName -> { keyName }
import styles from './Button.module.css';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);

render() {
  // 값의 변수명을 같게 만들기도 하고, state의 loading key를 새로 받아(얕은 복사) 사용
  const { loading } = this.state;
  return (
    <button
      onClick={this.startLoading}
      className={cx('button', { loading })}
      {...this.props}
    />
  );
}