본문 바로가기

CS/개발지식

Sass (Syntactically Awesome Style Sheets)

Sass

- CSS pre-processor 로써, 복잡한 작업을 쉽게 할 수 있게 해주고, 코드의 재활용성을 높여줄 뿐만 아니라, 코드의 가독성을 높여주어 유지보수를 쉽게 해줌.

 

Sass / 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;
}
보통은 scss 문법이 더 많이 사용됨.

 

Sass 사용하기

 

리액트 프로젝트 만들기

$ npx create-react-app styling-with-sass

 

node-sass 라는 라이브러리 설치하기

$ yarn add node-sass
$ npm i node-sass
sass 를 css 로 변환해주는 역할을 하는 라이브러리임.

 

Button 예제

 

기본 버튼 만들기

Button.jsx

import React from "react";
import "./Button.scss";

const Button = ({ children }) => {
  return <button className="Button">{children}</button>;
};

export default Button;

 

Button.scss

$blue: #228be6; // 주석 선언

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  line-height: 2.25rem;
  border: none;
  cursor: pointer;

  height: 2.25rem;
  padding-left: 1rem;
  padding-right: 1rem;
  font-size: 1rem;

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

  &:active {
    background: darken($blue, 10%); // 색상 10% 어둡게
  }
}
1. $blue : #228be6; 과 같이 스타일 파일에서 사용할 수 있는 변수를 선언할 수 있음.
2. lighten() 과 darken() 같은 색상을 더 밝게하거나 어둡게  해주는 함수를 사용할 수 있음.

 

App.js

import Button from './components/Button';
import "./App.scss"

function App() {
  return (
    <div className="App">
      <div className="buttons">
        <Button>BUTTON</Button>
      </div>
    </div>
  );
}

export default App;

 

App.scss

.App {
    width: 512px;
    margin: 0 auto;
    margin-top: 4rem;
    border: 1px solid black;
    padding: 1rem;
  }

 

버튼 사이즈 조정하기

버튼 크기에 large, medium, small 을 설정할 수 있도록 구현

 

classnames 라이브러리 사용하기

classNames 를 사용하면 조건부 스타일링을 할 때 함수의 인자에 문자열, 배열, 객체 등을 전달하여 손쉽게 문자열을 조합할 수 있음.

설치하기

$ yarn add classnames
$ npm i 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'

// 동시에 여러개의 타입으로 받아올 수 도 있습니다.
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// false, null, 0, undefined 는 무시됩니다.
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

 

Button.jsx (classnames 사용전)

import React from "react";
import "./Button.scss";

function Button({ children, size }) {
  return <button className={`Button ${size}`}>{children}</button>;
}

Button.defaultProps = {
  size: "medium",
};

export default Button;
defaultProps 를 사용해서 size 의 기본 값을 medium 으로 설정했음.

 

Button.jsx (classnames 사용후)

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

function Button({ children, size }) {
  return <button className={classNames('Button', size)}>{children}</button>;
}

Button.defaultProps = {
  size: "medium",
};

export default Button;

 

Button.scss

$blue: #228be6;

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

  background: $blue;
  &:hover {
    background: lighten($blue, 10%);
  }

  &:active {
    background: darken($blue, 10%);
  }
}
& 가 의미하는 것 (Parent Selector)

.Button {
  &.large {

  }
}​
.Button.large {

}

 부모를 선택한다고 생각하면 된다.

 

App.js

import Button from './components/Button';
import "./App.scss"

function App() {
  return (
    <div className="App">
      <div className="buttons">
        <Button size="small">BUTTON</Button>
        <Button>BUTTON</Button>
        <Button size="large">BUTTON</Button>
      </div>
    </div>
  );
}

export default App;

버튼 간격 조정하기

 

Button.scss

$blue: #228be6;

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

  background: $blue;
  &:hover {
    background: lighten($blue, 10%);
  }

  &:active {
    background: darken($blue, 10%);
  }

  & + & {
    margin-left: 1rem;
  }
}
& + &  = .Button + .Button

 

버튼 색상 설정하기

버튼의 색상에 blue, gray, pink 색을 설정 할 수 있도록 구현

 

Button.jsx

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

function Button({ children, size, color }) {
  return <button className={classNames('Button', size, color)}>{children}</button>;
}

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

export default Button;

 

Button.scss

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

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

   // 색상 관리
   &.blue {
    background: $blue;
    &:hover {
      background: lighten($blue, 10%);
    }

    &:active {
      background: darken($blue, 10%);
    }
  }

  &.gray {
    background: $gray;
    &:hover {
      background: lighten($gray, 10%);
    }

    &:active {
      background: darken($gray, 10%);
    }
  }

  &.pink {
    background: $pink;
    &:hover {
      background: lighten($pink, 10%);
    }
  } 

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

 

 

Mixins 사용하기

반복이 되는 코드를 Sass 의 mixin 이라는 기능을 사용하여, 쉽게 재사용할 수 있음.

 

반복된 코드

  &.blue {
    background: $blue;
    &:hover {
      background: lighten($blue, 10%);
    }

    &:active {
      background: darken($blue, 10%);
    }
  }

  &.gray {
    background: $gray;
    &:hover {
      background: lighten($gray, 10%);
    }

    &:active {
      background: darken($gray, 10%);
    }
  }

  &.pink {
    background: $pink;
    &:hover {
      background: lighten($pink, 10%);
    }

    &:active {
      background: darken($pink, 10%);
    }
  }

mixin 사용해서 정리한 모습

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

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

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

 

Button.scss (mixin 사용 후)

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

@mixin button-color($color){
    background: $color;
    &:hover {
        background: lighten($color, 10%);
      }
      &:active {
        background: darken($color, 10%);
      }
}

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

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

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

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

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

 

App.js

import Button from './components/Button';
import "./App.scss"

function App() {
  return (
    <div className="App">
      <div className="buttons">
        <Button size="large">BUTTON</Button>
        <Button>BUTTON</Button>
        <Button size="small">BUTTON</Button>
      </div>
      <div className="buttons">
        <Button size="large" color="gray">
          BUTTON
        </Button>
        <Button color="gray">BUTTON</Button>
        <Button size="small" color="gray">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="pink">
          BUTTON
        </Button>
        <Button color="pink">BUTTON</Button>
        <Button size="small" color="pink">
          BUTTON
        </Button>
      </div>
    </div>
  );
}

export default App;
className = "buttons" 위아래 마진을 주고 싶다면,
App.scss
.App {
    width: 512px;
    margin: 0 auto;
    margin-top: 4rem;
    border: 1px solid black;
    padding: 1rem;
    .buttons + .buttons {
        margin-top: 1rem;
    }
  }​

 

버튼에 outline 추가하기

outline 이라는 옵션을 주면 버튼에서 테두리만 보여지도록 구현

 

Button.jsx

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

function Button({ children, size, color, outline }) {
  return (
    <button className={classNames("Button", size, color, { outline })}>
      {children}
    </button>
  );
}

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

export default Button;
다른 classnames 들과 다르게 객체 안에 집어 넣는다면,
outline 값이 ture 일 경우에만 클래스가 적용된다.

 

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 {
        color: $color;
        background: none;
        border: 1px solid $color;
        &:hover{
            background: $color;
            color: white;
        }
    }  
}

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

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

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

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

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

 

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>
        <Button>BUTTON</Button>
        <Button size="small">BUTTON</Button>
      </div>
      <div className="buttons">
        <Button size="large" color="gray">
          BUTTON
        </Button>
        <Button color="gray">BUTTON</Button>
        <Button size="small" color="gray">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="pink">
          BUTTON
        </Button>
        <Button color="pink">BUTTON</Button>
        <Button size="small" color="pink">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="blue" outline>
          BUTTON
        </Button>
        <Button color="gray" outline>
          BUTTON
        </Button>
        <Button size="small" color="pink" outline>
          BUTTON
        </Button>
      </div>
    </div>
  );
}

export default App;

 

 

버튼을 전체 너비 차지하게 하기

fullWidth 라는 옵션이 있으면 버튼이 전체 너비를 차지하도록 구현

 

Button.jsx

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

function Button({ children, size, color, outline, fullWidth }) {
  return (
    <button className={classNames("Button", size, color, { 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 {
        color: $color;
        background: none;
        border: 1px solid $color;
        &:hover{
            background: $color;
            color: white;
        }
    }  
}

.Button {
  display: inline-flex;
  color: white;
  font-weight: bold;
  outline: none;
  border-radius: 4px;
  border: none;
  cursor: pointer;

  // 사이즈 관리
  &.large {
    height: 3rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.25rem;
    line-height: 3rem;
  }

  &.medium {
    height: 2.25rem;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1rem;
    line-height: 2.25rem;
  }

  &.small {
    height: 1.75rem;
    font-size: 0.875rem;
    padding-left: 1rem;
    padding-right: 1rem;
    line-height: 1.75rem;
  }

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

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

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

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

  &.fullWidth {
    width: 100%;
    justify-content: center;
    & + & {
      margin-left: 0;
      margin-top: 1rem;
    }
  }
}

 

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>
        <Button>BUTTON</Button>
        <Button size="small">BUTTON</Button>
      </div>
      <div className="buttons">
        <Button size="large" color="gray">
          BUTTON
        </Button>
        <Button color="gray">BUTTON</Button>
        <Button size="small" color="gray">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="pink">
          BUTTON
        </Button>
        <Button color="pink">BUTTON</Button>
        <Button size="small" color="pink">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="blue" outline>
          BUTTON
        </Button>
        <Button color="gray" outline>
          BUTTON
        </Button>
        <Button size="small" color="pink" outline>
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" fullWidth>
          BUTTON
        </Button>
        <Button size="large" fullWidth color="gray">
          BUTTON
        </Button>
        <Button size="large" fullWidth color="pink">
          BUTTON
        </Button>
      </div>
    </div>
  );
}

export default App;

 

 

버튼에 ...rest props 전달하기

버튼에 onClick 이벤트를 설정하기 위해서 구현

 

Button.jsx

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

function Button({ children, size, color, outline, fullWidth, onClick }) {
  return (
    <button
      className={classNames("Button", size, color, { outline, fullWidth })}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

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

export default Button;
onMouseMove() 이벤트와 onClick() 이벤트를 둘 다 주고 싶은 그런 상황에는 , spread 와 rest 문법을 사용하자

 

Button.jsx

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

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

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

export default Button;
...rest 를 사용하면 우리가 지정한 props 를 제외한 값들을 rest 라는 객체에 모아준다.

 

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" onClick={() => console.log('클릭됐다!')}>
          BUTTON
        </Button>
        <Button>BUTTON</Button>
        <Button size="small">BUTTON</Button>
      </div>
      <div className="buttons">
        <Button size="large" color="gray">
          BUTTON
        </Button>
        <Button color="gray">BUTTON</Button>
        <Button size="small" color="gray">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="pink">
          BUTTON
        </Button>
        <Button color="pink">BUTTON</Button>
        <Button size="small" color="pink">
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" color="blue" outline>
          BUTTON
        </Button>
        <Button color="gray" outline>
          BUTTON
        </Button>
        <Button size="small" color="pink" outline>
          BUTTON
        </Button>
      </div>
      <div className="buttons">
        <Button size="large" fullWidth>
          BUTTON
        </Button>
        <Button size="large" color="gray" fullWidth>
          BUTTON
        </Button>
        <Button size="large" color="pink" fullWidth>
          BUTTON
        </Button>
      </div>
    </div>
  );
}

export default App;

어떤 props 를 받아와야할 지 확실치 않은 상황에서 HTML 태그에게 전달을 해주어야하는 상황에는 ...rest 문법을 사용하면 좋다.

 

정리

- Sass 을 사용하면 스타일 파일에 유용한 문법을 사용하여 컴포넌트 스타일링 생산성을 높일 수있다.

'CS > 개발지식' 카테고리의 다른 글

개발환경 설정 - Webpack  (2) 2022.12.28
크로스 브라우징 (Cross browsing)  (0) 2022.12.26
렌더링 엔진  (0) 2022.12.26
웹 브라우저의 동작 순서  (0) 2022.12.26
코드 리팩토링와 클린 코드  (0) 2022.12.26