[React] 컴포넌트 스타일링

vanLan·2022년 11월 25일
0

React

목록 보기
9/11
post-thumbnail


🥇 컴포넌트 스타일링

  • 리액트에서 컴포넌트를 스타일링 할 때는 다양한 방식을 사용할 수 있다.
    (회사마다, 개발자마다, 각자 취향에 따라 선택한다.)

    🥉 스타일링의 종류

    • 일반 CSS: 컴포넌트를 스타일링하는 가장 기본적인 방식.
    • Sass: 자주 사용되는 CSS 전처리기(pre-processor) 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있다.
    • CSS Module: 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해주는 옵션.
    • styled-components: 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 도와준다.


🥈 일반 CSS(가장 흔한 방식)

  • create-react-app으로 생성한 프로젝트는 일반 CSS 방식으로 만들어져 있다, 기존의 CSS 스타일링이 딱히 불편하지 않고 새로운 기술을 배울 필요가 없다고 생각되면, 일반 CSS를 계속 사용해도 상관 없다.

  • 소규모 프로젝트를 개발하고 있다면 새로운 스타일링 시스템을 적용하는 것이 불필요할 수도 있다.

  • CSS를 작성할 때 가장 중요한 점은 CSS의 클래스를 중복되지 않게 하는 것이다.

    🥉 네이밍 규칙

    • 컴포넌트 이름-클래스 형태(예: App-header)
    • BEM 네이밍: 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식(예: card__title-primary)

    🥉 CSS Selector

    • CSS Selector를 사용하면 특정 클래스 내부에 있는 CSS클래스 에만 스타일을 적용 시킬 수도 있다.

      
      .App .logo {
        animation: App-logo-spin infinite 20s linear;
        height: 40vmin;
      }

🥈 Sass 사용하기

  • Sass(Syntactically Awesome Style Sheets)는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높여줄 뿐 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 도와준다.

  • create-react-app 구버전에서는 Sass를 사용하려면 추가 작업이 필요하였으나, v2 버전 이후 부터는 별도의 추가 설정 없이 바로 사용이 가능하다.

    🥉 .scss.sass 두 가지 확장자를 지원

    • .sass 확장자는 중괄호{} 와 세미콜론;을 사용하지 않으나, .scss 확장자는 기존 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않다. 보통 .scss 문법이 더 자주 사용된다.

        
      // .sass
      $font-stack: Helvetica, sans-serif
      $primary-color: #333
      
      body 
        font: 100% $font-stack
        color: $primary-color
      
      
      // .scss
      $font-stack: Helvetica, sans-serif;
      $primary-color: #333;
      
      body {
        font: 100% $font-stack;
        color: $primary-color;
      }

    🥉 Sass 라이브러리 설치 방법

    • yarn 사용시
      $ yarn add sass
    • npm 사용시
      $ npm install node-sass

    🥉 Sass 분리하여 사용하기

    • Sass 변수 및 믹스인은 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용하는 것이 좋다.

      
      // 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.scss';
      
      .SassComponent {
        display: flex;
        .box { // 일반 CSS에서는 .SassComponent .box 와 같음.
          cursor: pointer;
          transition: all 0.3 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 {
            background: black;
          }
        }
      }

    🥉 Sass 라이브러리 사용하기

    • 설치
      $ yarn add [라이브러리 이름]

    • 불러오기

      • 상대경로 이용시
        @import '../../../node_modules/library/styles';
      • 물결 문자(~) 사용
        @import '~library/styles';
        // 물결 문자를 사용하면 자동으로 node_modules에서 라이브러리 디렉토리를 탐지하여 스타일을 불러올 수 있다.
    • 예시
      include-media: 반응형 디자인을 쉽게 만들어 줌
      open-color: 색상 팔레트

      • 설치
        $ yarn add open-color include-media

      • 불러오기

        
        // 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
        
        @import './styles/utils.scss';
        
        .SassComponent {
          display: flex;
          background: $oc-gray-2;
          @include media('<768px') {
            background: $oc-gray-9;
          }
          .box { // 일반 CSS에서는 .SassComponent .box 와 같음.
            cursor: pointer;
            transition: all 0.3 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 {
              background: black;
            }
          }
        }

🥈 CSS Module

  • CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값
    [파일이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술.

  • 구버전 create-react-app에서는 웹팩에서 css-loader 설정을 별도로 해주어야 했지만, v2 버전 이후부터는 .module.css 확장자로 파일을 저장하면 CSS Module이 적용.

    🥉 CSS Module 사용법

    • 클래스를 적용하고 싶은 JSX 엘리먼트에 ClassName={styles.[클래스 이름]} 형태로 전달.
      :global을 사용하여 전역적으로 선언한 클래스의 경우 평상시 처럼 문자열로 입력.
      
      /* CSSModule.module.css */
      
      /* 자동으로 고유해 지므로, 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 */
      .wrapper {
        background: black;
        padding: 1rem;
        color: white;
        font-size: 2rem;
      }
      
      /* 글로벌 CSS 작성시 */
      :global .somethig {
        font-weight: 800;
        color: aqua;
      }
      
      // CSSModule.js
      
      import styles from './CSSModule.module.css';
      const CSSModule = () => {
        return (
          <div className='{styles.wrapper}'>
            이것이 바로, <span className='something'>CSS Module!</span>
          </div>
        );
      };
      
      export default CSSModule;

    🥉 CSS Module을 사용한 클래스 이름을 두개 이상 적용

    • 클래스를 적용하고 싶은 JSX 엘리먼트에 템플릿 리터럴(Template Literal)을 사용하여 입력.

      
      /* CSSModule.module.css */
      
      /* 자동으로 고유해 지므로, 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 */
      .wrapper {
        background: black;
        padding: 1rem;
        color: white;
        font-size: 2rem;
      }
      
      .inverted {
        color: black;
        background: white;
        border: 1px solid black;
      }
      
      /* 글로벌 CSS 작성시 */
      :global .somethig {
        font-weight: 800;
        color: aqua;
      }
      
      // CSSModule.js
      
      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;

    🥉 classnames 라이브러리

    • classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리이다.
    • CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리하다.
      • 설치
        $ 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'
        
        const myClass = 'hello';
        classNames('one', myClass, { myCondition: true }); // = 'one hello myCondition'

        위와 같이 여러 가지 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있기 때문에 컴포넌트에서 조건부 클래스를 설정할 때 매우 편리하다.

        
        // props 값에 따라 다른 스타일 적용
        const MyComponent = ({ highlighted, theme }) => (
          <div className={classNames('MyComponent', { highlighted }, theme)}>Hello</div>
        );

        위와같이 작성한 경우, 엘리먼트의 클래스에 highlighted 값이 true이면 highlighted 클래스가 적용되고, false이면 적용되지 않는다. theme로 전달받는 문자열은 내용 그대로 클래스에 적용된다.

        
        import classNames from 'classnames/bind';
        import styles from './CSSModule.module.css';
        
        const cx = classNames.bind(styles); // 미리 styles에서 클래스를 받아 오도록 설정
        const CSSModule = () => {
          return (
            <div className={cx('wrapper', 'inverted')}>
              이것이 바로, <span className='somthing'>CSS Module!</span>
            </div>
          );
        };
        
        export default CSSModule;

        위와 같이 classnames에 내장되어 있는 bind함수를 사용하면 클래스를 넣어 줄 때마다 styles.[클래스 이름] 형태를 사용할 필요가 없다.
        사전에 미리 styles에서 받아 온 후 사용하게 설정해 두고 cx('클래스 이름', '클래스 이름2') 형태로 사용할 수 있다.

    🥉 Sass와 함께 사용하기

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

      
      /* 자동으로 고유해 지므로, 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 */
      .wrapper {
        background: black;
        padding: 1rem;
        color: white;
        font-size: 2rem;
        &.inverted {
          // inverted가 .wrapper와 함께 사용되었을 때만 적용.
          color: black;
          background: white;
          border: 1px solid black;
        }
      }
      
      /* 글로벌 CSS를 작성하고 싶다면 */
      :global {
        .something {
          font-weight: 800;
          color: aqua;
        }
        // 여기에 다른 클래스 작성 가능
      }

    🥉 CSS Module이 아닌 파일에서 CSS Module 사용하기

    • CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼 CSS Module이 아닌 일반 .css, .scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있다.

      
      :local .wrapper {
        /* 스타일 */
      }
      :local {
        .wrapper {
          /* 스타일 */
        }
      }

🥈 styled-components

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

  • CSS-in-JS 라이브러리 중 개발자들이 가장 선호하는 방식 이다.

    🥉 styled-components 사용하기

    • 자바스크립트 파일 하나에 스타일까지 작성하기 때문에 .css 또는 .scss 확장자의 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.

      • 설치
        $ yarn add styled-componets

      • 간략한 사용법

        
        import styled, { css } from 'styled-components';
        
        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.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;
      • 장점
        props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다.

      • VS Code 사용시 추천 확장프로그램
        vscode-styled-components: styled-components를 위해 내부에 작성한 스타일이 그저 문자열로 간주되어 코드 Syntax 하이라이팅이 제대로 이루어지지 않는 문제를 해결

    🥉 Tagged 템플릿 리터럴

    • styled-components에서 스타일을 작성할 때 `을 사용하여 만든 문자열에 스타일 정보를 넣어주었다. 여기서 사용한 문법을 Tagged 템플릿 리터럴이라고 한다.

    • 일반 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출 할 수 있다는 것이다.

      
      function tagged(...args) {
        console.log(args);
      }
      
      tagged`hello ${{foo: 'bar'}} ${() => 'world'}!`
      // 1. ['hello', '', '!']
      // 2. {foo: 'bar'}
      // 3. () => 'world'
      • Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이사이에 들어가는 자바스크립트 객체나 함수의 원본 값을 그대로 추출할 수 있다.
      • styled-components는 이러한 속성을 사용하면 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회 할 수 있도록 해준다.

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

    • styled-components를 사용하여 스타일링된 엘리먼트를 만드려면 컴포넌트 파일의 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현한다.

      
      import styled from 'styled-components';
      
      const MyComponent = styled.div`
        font-size: 2rem;
      `;
      • 위 코드와 같이 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면, 해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성되어 <MyComponent>Hello</MyComponent>와 같은 형태로 사용할 수 있다.
      • div가 아닌 button이나 input에 스타일링을 하고 싶다면 styled.button 혹은 styled.input 같은 형태로 뒤에 태그명을 넣어주면 된다.
      
      // 태그의 타입을 styled 함수의 인자로 전달
      const MyInput = styled('input')`
        background: gray;
      `
      
      // 컴포넌트 형식의 값을 넣어줌
      const StyledLink = styled(Link)`
        color: blue;
      `
      • 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링을 주고 싶다면 다음과 같은 형태로 구현할 수 있다.

      💡노트

      • 위에서 나오는 Link 컴포넌트는 리액트 라우터에서 사용할 컴포넌트이다. 이런 식으로 컴포넌트를 styled의 파라미터에 넣는 경우에는 해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정하는 작업이 내부적으로 되어있어야 한다.

        const Sample = ({ className }) => {
          return <div className={className}></div>
        };
        
        const StyledSample = styled(Sample)`
          font-size: 2rem;
        `

    🥉 스타일에서 props 조회하기

    • styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있다.
      const Box = styled.div`
        // props로 넣어준 값을 직접 전달해 줄 수 있다.
        background: ${props => props.color || 'blue'};
        padding: 1rem;
        display: flex;
      `
      • 위 코드를 보면 background 값에 props를 조회해서 props.color 값을 사용하고 props.color 값이 주어지지 않았을 경우 기본값을 blue로 설정하였다.
        이렇게 만들어진 코드는 JSX에서 사용될 때 color 값을 props로 넣어 줄 수 있다.
        <Box color='black'>(...)</Box>

    🥉 props에 따른 조건부 스타일링

    • styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있다.

      
      import styled, { css } from 'styled-components';
      
      // 단순 변수 형태가 아니라 여러 줄의 스타일 구문을 조건부로 설정해야 하는 경우에는 css를 불러와야 한다.
      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;
        }
      `;
      • 다음과 같이 props를 사용하여 서로 다른 스타일을 적용할 수있다.

        
        <Button>안녕하세요</Button>
        <Button inverted={true}>테두리만</Button>
      • 스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS를 styled-components에서 불러와야 한다.

      • CSS를 사용하지 않고 바로 문자열을 넣어도 작동하기는 하지만, 해당 내용이 문자열로만 취급되기 때문에 VS Code 확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않고 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다.

      • 조건부 스타일링을 할 때 props 참조하면 반드시 CSS로 감싸 주어서 Tagged 템플릿 리터럴을 사용해 주어야 한다.

    🥉 반응형 디자인

    • 일반 CSS를 사용할 때와 똑같이 media 쿼리(query)를 사용한다.

      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%;
        }
      `
    • 여러 컴포넌트에서 반복 될 경우 함수화하여 간편하게 사용할 수 있다.

      
      import styled, { css } from 'styled-components';
      
      const sizes = {
        desktop: 1024,
        tablet: 768
      };
      
      // sizes 객체에 따라 자동으로 media 쿼리 함수를 만들어 준다.
      const media = Object.keys(sizes).reduce((acc, label) => {
        acc[label] = (...args) => css`
          @media(max-width: ${sizes[label] / 16}em) {
            ${css(...args)}
          }
        `;
      }, {});
      
      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%;`};
      `;
      • 실제 사용시, media를 다른 파일로 모듈화한 후 불러와 사용하는 방식으로 효율적이게 사용할 수 있다.


📀 정리

  • 여러 방식들 중 무엇을 사용할지 선택하는 것은 나의 몫이다.


[참고] 김민준(Velopert)님의 '리액트를 다루는 기술'을 공부하며 정리한 내용임.

profile
프론트엔드 개발자를 꿈꾸는 이

0개의 댓글