React기본) React Component Styling 1

lbr·2022년 8월 15일
0

Style Loaders

CSS, SASS 방식

react에서 css를 import한 결과물을 보면, import 순서대로 컴파일되어 html 파일에 전역적으로 정의되어 있는 것을 확인할 수 있습니다.

전역적으로 추가되어 있다는 점이 문제가 되는 부분입니다.
react의 특성인 컴포넌트 별로 구분되어야 할 문서구조, 동작, 스타일에서 style이 컴포넌트에 귀속된 구조로 제공되지 않는 다는 것을 의미합니다.

react에서는 이런 style이 컴포넌트에 귀속된 구조로 만들어 주는 기능을 제공하고 있지 않습니다.
react 자체적으로는 컴포넌트별로 독립적으로 css의 scoping 해주는 방법이 제공되고 있지 않습니다.

그래서, 이렇게 css(또는 sass)를 import하는 방식으로 개발할 때에는 가장 주의해야할 점이 style를 주려고 하는 class가 전역적으로 오염되지 않도록 하는 것이 제일 중요합니다.

기계적으로 자동화되는 부분이 아니라서 팀 내부적으로 규칙을 정하고, 그 규칙에 맞게 class를 사용하는 그런 방법론이 생기기 시작했습니다.

BEM

http://getbem.com/
class Naming 방법론.

BEM은 Block, Element, Modifier의 줄임말로 CSS 방법론 중 하나로, 스타일링 구조를 정의한다.

기본 구조는 block__element--modifier이다.

react에서 컴포넌트 별로 scoping이 되지 않기 때문에 팀 내에서 이런 방법론을 정해서 이 규칙을 따르자고 팀 내의 규칙을 정하는 것입니다.

참고

sass로 컴파일이 되지 않는다면, sass를 css로 변환해 주는 모듈이 설치되어 있지 않기 때문입니다.
현재는 create-react-app에는 이 모듈이 설치되어 있지 않습니다.
npm i sass 로 추가합니다.

CSS module, SASS module 방식

create-react-app 의 공식 사이트에서 CSS Modules와 관련된 내용을 확인할 수 있습니다.
https://create-react-app.dev/docs/adding-a-css-modules-stylesheet

module 방식이 아닌 기존의 CSS또는 SASS 방식을 import 방식으로 스타일을 적용할 때에는 import 순으로 전역적으로 적용되는 방식이었습니다. 이렇게 될 경우에는 앞에서 말한대로 scope가 오염될 수 있는 단점이 있었습니다.

이런 단점을 극복하기 위해서
web-pack을 이용해서 이번에 배울 module을 import할 때 오염이 되지 않도록 자동화기능이 추가된 형태의 css module 방식이 있습니다.

사용방법

import문에서 아래처럼 확장자 앞에 module을 붙여서 추가합니다.

import styles from "./App.module.css"

설명

styles를 console.log 로 출력해 보면 아래와 같은 객체 안에 속성들이 보입니다.

위의 값 패턴을 보면 App: App_App__16ZpL 이런식으로 나옵니다.

create-react-app 의 공식사이트에서 그 의미를 확인할 수 있습니다.

[filename]\_[classname]\_\_[hash]

filename : css 파일 이름
classname : class 이름
hash : hash 값

이로써 module 방식은 2가지 일을 한다는 것을 알 수 있습니다.

  1. 우리가 작성한 css를 전역으로 추가합니다.
  2. 우리가 작성한 class이름을 [filename]\_[classname]\_\_[hash] 방식으로 바꾸어 매칭시킵니다.

연습

// Button.jsx
// button을 누르면 button의 class이름을 바꿔줄 것입니다.
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 } />; 
  }
  
  //event에 바인딩 되는 함수는 화살표함수를 사용해야 화살표함수 안에서 this를 사용할 수 있습니다.
  startLoading = () => {
    this.setState({ loading: true, });
    setTimeout(() => {
      this.setState({
        loading: false,
      });
    }, 1000);
  }
}

export default Button;
/* Button.module.css */
.button {
  background: 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;
}

위 코드로 클래스 이름을 event에 따라 바꿔줄 수 있습니다.
위의 코드에서 class 이름을 설정해 주는 코드가 상당히 길고 복잡해 보입니다.

<button onClick={ this.startLoading } className={this.state.loading ? `${styles["button"]} ${styles["loading"]}` : styles["button"]} { ... this.props } />; 

그래서 위의 클래스 이름을 설정해주는 코드를 쉽게 사용할 수 있게 도와주는 라이브러리가 있습니다.

classnames 라이브러리

설치

npm i classnames

사용방법

import

import classNames from 'classnames';

우선 사용예 미리보기1

console.log(classNames('foo', 'bar'));

위 코드로 출력된 log를 보면 아래와 같이 나옵니다.

foo와 bar가 한 칸 띄어진 형태로 나옵니다.

우선 사용예 미리보기2

console.log(classNames({ foo: true }, { bar: false }));

위 코드로 출력된 log를 보면 아래와 같이 나옵니다.

우선 사용예 미리보기3

console.log(classNames(null, false, "bar", undefined, 0, 1, { baz: null }, ""));

위 코드로 출력된 log를 보면 아래와 같이 나옵니다.

우리 코드에 적용

<button 
  onClick={ this.startLoading } 
  className={this.state.loading ? `${styles["button"]} ${styles["loading"]}` : styles["button"]} 
  { ... this.props } 
/>; 

위 코드를 classnames 라이브러리를 적용하면 아래와 같습니다.

<button 
  onClick={ this.startLoading } 
  className={this.state.loading ? classNames(styles["button"], styles["loading"]) : styles["button"]} 
  { ... this.props } 
/>; 

하지만 코드 길이가 큰 차이가 없고, 생각보다 그렇게 편해지지는 않았습니다.

아래처럼 바꾸면 좀 더 코드를 간결하게 정리할 수 있겠지만.. 속성이름으로 styles[] 를 넣을 수 없습니다. 그래서 classnames 라이브러리에서는 해당 부분을 해결하기 위해 bind 방법을 제공합니다.

<button 
  onClick={ this.startLoading } 
  className={classNames(styles["button"], { styles["loading"]: this.state.loading })} 
  { ... this.props } 
/>; 

최종 우리 코드에 적용

import

import styles from "./Button.module.css";
import classNames from "classnames/bind";

const cx = classNames.bind(styles);

classnames/bind 로 부터 가져옵니다.
classNames.bind(styles)의 반환값 cx 변수를 이용합니다.

const {loading} = this.state;

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

참고로 { loading: loading }은 js 문법으로 { loading }으로 줄일 수 있습니다.

loading 에 true 값이 들어오면 loading 이 클래스로 같이 추가되며, false가 들어오면 추가되지 않습니다.

sass(scss)도 사용법이 다르지 않습니다.

0개의 댓글