2장. 리액트 컴포넌트 스타일링하기

hyebin Jo·2022년 7월 12일
0

1. Sass

시작하기

node-sass 라이브러리 설치

$blue: #228be6; // ⭐css 값을 변수로 선언할 수 있습니다.⭐

.Button {
  ...
  
  background: $blue; // 주석 사용
  &:hover {
    background: lighten($blue, 10%); // 색상 10% 밝게
  }

  &:active {
    background: darken($blue, 10%); // 색상 10% 어둡게
  }
}

className 에 CSS 클래스 이름을 동적으로 넣기

className={['Button', size].join(' ')}
className={`Button ${size}`}

classnames 라이브러리 사용하여 손쉽게 문자열을 조합 할 수 있습니다.

classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'
classNames(['foo', 'bar']); // => 'foo bar'

mixin

mixin을 사용하여 반복되는 코드들을 재사용할 수 있습니다.

$blue: #228be6;
$gray: #495057;
$pink: #f06595;

//button-color라는 mixin을 만들고
@mixin button-color($color) {
  background: $color;
  &:hover {
    background: lighten($color, 10%);
  }
  &:active {
    background: darken($color, 10%);
  }
}

.Button {
  ...

  // 사이즈 관리
  &.large {
    ...
  }

  &.medium {
    ...
  }

  &.small {
    ...
  }

  // 색상 관리
  &.blue {
    @include button-color($blue);
  }

  &.gray {
    @include button-color($gray);
  }

  &.pink {
    @include button-color($pink);
  }
  //@include 를 사용하여 button-color라는 믹스인을 사용

  & + & {
    margin-left: 1rem;
  }
}

옵션 추가하기

//Button.js

import React from 'react';
import classNames from 'classnames';
import './Button.scss';

function Button({ children, size, color, outline, fullWidth }) {
  return (
    <button
      className={classNames('Button', size, color, { outline, fullWidth })}
    > // outline, fullWidth 옵션 추가
      {children}
    </button>
  );
}

Button.defaultProps = {
  size: 'medium',
  color: 'blue'
};

export default Button;
//Button.scss

$blue: #228be6;
$gray: #495057;
$pink: #f06595;

@mixin button-color($color) {
  background: $color;
  &:hover {
    background: lighten($color, 10%);
  }
  &:active {
    background: darken($color, 10%);
  }
  &.outline {
    ...
    border: 1px solid $color;
  }
}

.Button {
  ...

  // 사이즈 관리
  &.large {
    ...
  }

  &.medium {
    ...
  }

  &.small {
    ...
  }

  // 색상 관리
  &.blue {
    @include button-color($blue);
  }

  &.gray {
    @include button-color($gray);
  }

  &.pink {
    @include button-color($pink);
  }

  & + & {
    margin-left: 1rem;
  }

  &.fullWidth {
    ...
  }
}
//App.js

import React from 'react';
import './App.scss';
import Button from './components/Button';

function App() {
  return (
    <div className="App">
      <div className="buttons">
        <Button size="large">BUTTON</Button> //large, blue(default)
        <Button>BUTTON</Button> //medium(default), blue(default)
        <Button size="small">BUTTON</Button> //small, blue(default)
      </div>
      <div className="buttons">
        <Button size="large" color="gray"> //large, gray
          BUTTON
        </Button>
        <Button color="gray">BUTTON</Button> //medium(default), gray
        <Button size="small" color="gray"> //small, gray
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="pink"> //large, pink
          BUTTON
        </Button>
        <Button color="pink">BUTTON</Button> //medium(default), pink
        <Button size="small" color="pink">
          BUTTON
        </Button> //small, pink
      </div>
      <div className="buttons">
        <Button size="large" color="blue" outline>
          BUTTON
        </Button> //large, blue, outline(option)
        <Button color="gray" outline>
          BUTTON
        </Button> //medium(default), gray, outline(option)
        <Button size="small" color="pink" outline>
          BUTTON
        </Button> //small, pink, outline(option)
      </div>
      <div className="buttons">
        <Button size="large" fullWidth>
          BUTTON
        </Button>
        <Button size="large" fullWidth color="gray">
          BUTTON
        </Button> //large, gray, fullWidth(option)
        <Button size="large" fullWidth color="pink">
          BUTTON
        </Button> //large, pink, fullWidth(option)
      </div>
    </div>
  );
}

export default App;

...rest props 전달하기

function Button({ children, size, color, outline, fullWidth, ...rest }) {
  return (
    <button
      className={classNames('Button', size, color, { outline, fullWidth })}
      {...rest}
    >
      {children}
    </button>
  );
}
function App() {
  return (
    <Button size="large" onClick={() => console.log('클릭됐다!')}>
      BUTTON
    </Button>)
}

컴포넌트가 어떤 props 를 받을 지 확실치는 않지만 그대로 다른 컴포넌트 또는 HTML 태그에 전달을 해주어야 하는 상황에 ...rest 문법을 활용!

02. CSS Module

리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있습니다.

//사용법
import styles from "./Box.module.css";

function Box() {
  return <div className={styles.Box}>{styles.Box}</div>;
}

클래스 이름에 - 가 들어가 있을때 : styles['my-class']
클래스 이름이 여러개일때 : ${styles.one} ${styles.two}
조건부 스타일링을 해야 할 때 : ${styles.one} ${condition ? styles.two : ''}
👉 classnames 라이브러리의 bind 기능으로 CSS Module을 더 간편하게 쓸 수 있습니다.

//classnames라이브러리의 bind 사용법
import styles from './CheckBox.module.css';
import classNames from 'classnames/bind'; 

const cx = classNames.bind(styles);

function CheckBox({ children, checked, ...rest }) {
  return (
    <div className={cx('checkbox')}>
    ...
cx('one', 'two')
cx('my-component', {
  condition: true
})
cx('my-component', ['another', 'classnames'])

✅ CSS Module 을 사용하고 있는 파일에서 클래스 이름을 고유화 하지 않고 전역적 클래스이름을 사용하고 싶을 때

:global .my-global-name {

}
// Sass를 사용할때
:global {
  .my-global-name {

  }
}

✅ CSS Module 을 사용하지 않는 곳에서 특정 클래스에서만 고유 이름을 만들어서 사용하고 싶을 때

:local .make-this-local {

}
:local {
  .make-this-local {

  }
}

03. styled-components

styled-components 는 현존하는 CSS in JS 관련 리액트 라이브러리 중에서 가장 인기 있는 라이브러리입니다. 이에 대한 대안으로는 emotion 와 styled-jsx가 있습니다.

조건부 스타일링

import styled from 'styled-components';

const Circle = styled.div`
  ...
  background: ${props => props.color || 'black'};
`; //props설정에 따라 스타일링을 할 수 있다.

function App() {
  return <Circle color="blue" />;
}
import styled, { css } from 'styled-components';

const Circle = styled.div`
  ...
  background: ${props => props.color || 'black'};
  ${props =>
    props.huge &&
    css`
      width: 10rem;
      height: 10rem;
    `} 
`; //특정 props가 있을 때에만 설정하는 css

function App() {
  return <Circle color="red" huge />;
}

polished의 스타일 관련 유틸 함수 사용하기

import styled from 'styled-components';
import { darken, lighten } from 'polished';

const StyledButton = styled.button`
  /* 공통 스타일 */
  ...

  /* 크기 */
 ...

  /* 색상 */
  background: #228be6;
  &:hover {
    background: ${lighten(0.1, '#228be6')};
  }
  &:active {
    background: ${darken(0.1, '#228be6')};
  }

  /* 기타 */
  ...
`;

function Button({ children, ...rest }) {
  return <StyledButton {...rest}>{children}</StyledButton>;
}

export default Button;

Themeprovider

colorStyle 예시

import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';

const AppBlock = styled.div`
  ...
`;

function App() {
  return (
    <ThemeProvider
      theme={{
        palette: {
          blue: '#228be6',
          gray: '#495057',
          pink: '#f06595'
        }
      }}
    >
      <AppBlock>
        <Button>BUTTON</Button>
        <Button color="gray">BUTTON</Button>
        <Button color="pink">BUTTON</Button>
      </AppBlock>
    </ThemeProvider>
  );
}
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';

const StyledButton = styled.button`
  /* 공통 스타일 */
  ...

  /* 크기 */
  ...

  /* 색상 */
  ${props => {
    const selected = props.theme.palette[props.color];
    return css`
      background: ${selected};
      &:hover {
        background: ${lighten(0.1, selected)};
      }
      &:active {
        background: ${darken(0.1, selected)};
      }
    `;
  }}
  // ${({ theme, color }) => {
  //    const selected = theme.palette[color];
  //    return css`...
  // => 이렇게 비구조화 할당도 사용!
    
  /* 기타 */
  & + & {
    margin-left: 1rem;
  }
`;

function Button({ children, ...rest }) {
  return <StyledButton {...rest}>{children}</StyledButton>;
}

Button.defaultProps = {
  color: 'blue'
};

위 로직은 색상관련 코드를 분리하여 사용할 수 있습니다.

import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';

const colorStyles = css`
  ${({ theme, color }) => {
    const selected = theme.palette[color];
    return css`
      background: ${selected};
      &:hover {
        background: ${lighten(0.1, selected)};
      }
      &:active {
        background: ${darken(0.1, selected)};
      }
    `;
  }}
`;
// 색상 관련 스타일코드 따로 분리

const StyledButton = styled.button`
  /* 공통 스타일 */
  ...

  /* 크기 */
  ...

  /* 색상 */
  ${colorStyles} //따로 분리한 색상 관련 코드

  /* 기타 */
  & + & {
    margin-left: 1rem;
  }
`;

function Button({ children, color, ...rest }) {
  return <StyledButton color={color} {...rest}>{children}</StyledButton>;
}

Button.defaultProps = {
  color: 'blue'
};

sizeStyle 추가하기!

import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';

const colorStyles = css`
  ${({ theme, color }) => {
    기존 색상 관련 코드들...
  }}
`;

//사이즈 관련 코드도 추가!!
const sizeStyles = css`
  ${props =>
    props.size === 'large' &&
    css`
      height: 3rem;
      font-size: 1.25rem;
    `}

  ${props =>
    props.size === 'medium' &&
    css`
      height: 2.25rem;
      font-size: 1rem;
    `}

    ${props =>
      props.size === 'small' &&
      css`
        height: 1.75rem;
        font-size: 0.875rem;
      `}
`;

const StyledButton = styled.button`
  /* 공통 스타일 */
  ...

  /* 크기 */
  ${sizeStyles}

  /* 색상 */
  ${colorStyles} //따로 분리한 사이즈 관련 코드

  /* 기타 */
  & + & {
    margin-left: 1rem;
  }
`;

function Button({ children, color, size,  ...rest }) {
  return (
    <StyledButton color={color} size={size} {...rest}>
      {children}
    </StyledButton>
  );
}

Button.defaultProps = {
  color: 'blue',
  size: 'medium'
};

위 sizeStyles의 중복되는 코드 리팩토링하기

const sizes = {
  large: {
    height: '3rem',
    fontSize: '1.25rem'
  },
  medium: {
    height: '2.25rem',
    fontSize: '1rem'
  },
  small: {
    height: '1.75rem',
    fontSize: '0.875rem'
  }
};

const sizeStyles = css`
  ${({ size }) => css`
    height: ${sizes[size].height};
    font-size: ${sizes[size].fontSize};
  `}
`;

outline, fullWidth 옵션도 추가하기!

import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';

const colorStyles = css`
  ${({ theme, color }) => {
    const selected = theme.palette[color];
    return css`
      ...
      ${props =>
        props.outline &&
        css`
          ...
        `} //outline props가 추가됐을때의 색상관련 코드들
    `;
  }}
`;

const sizes = {
  ...
};

const sizeStyles = css`
  ...
`;

//fullWidthStyle관련 코드 추가!!
const fullWidthStyle = css`
  ${props =>
    props.fullWidth &&
    css`
      ...
    `}
`;

const StyledButton = styled.button`
  /* 공통 스타일 */
  ...

  /* 크기 */
  ${sizeStyles}

  /* 색상 */
  ${colorStyles}

  /* 기타 */
  ...

  ${fullWidthStyle} //따로 분리한 fullWidth관련 코드 
`;

function Button({ children, color, size, outline, fullWidth, ...rest }) {
  return (
    <StyledButton
      color={color}
      size={size}
      outline={outline}
      fullWidth={fullWidth}
      {...rest}
    >
      {children}
    </StyledButton>
  );
}

Button.defaultProps = {
  color: 'blue',
  size: 'medium'
};

styled-components로 컴포넌트의 스타일을 특정 상황에서 덮어쓰는 방법

import styled from 'styled-components';
import Button from './Button';

const ShortMarginButton = styled(Button)`
  & + & {
    margin-left: 0.5rem;
  }
`; //⭐Button컴포넌트에 스타일을 덮어쓰기

function Dialog({ title, children, confirmText, cancelText }) {
  return (
        <>
          ........
          <ShortMarginButton color="gray">{cancelText}</ShortMarginButton>
          <ShortMarginButton color="pink">{confirmText}</ShortMarginButton>
        </>
  );
}

Dialog.defaultProps = {
  confirmText: '확인',
  cancelText: '취소'
};

0개의 댓글