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에서 제공하고 있는 위계질서 (일치, 후손, 자식, 등등..)을 사용하면 됨
 
 - 방법01 : 이런 오염들을 막기 위해 CSS 방법론(Naming)이 생겨남 -> BEM (Block, Element, Modifier)
        
 
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}
    />
  );
}