컴포넌트 스타일링

정다윤·2023년 1월 31일
0

React

목록 보기
9/12
post-thumbnail

컴포넌트 스타일링 방식

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

CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지않게 만드는 것입니다.
CSS클래스가 중복되는 것을 방지하는 여러가지 방식이 있는데 그 중 하나는 이름을 지을때 특별한 규칙을 사용하여 짓는 것이고, 또 다른 하나는 CSS Selector를 활용하는 것 입니다.

이름 짓는 규칙

기본 규칙 : 컴포넌트 이름 - 클래스 형태 (ex : App-header)
BEM 네이밍 : 일종의 규칙을 준수항 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식 (ex : .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 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고 스타일 코드의 재활용성을 높여줄 뿐만 아니라 코드의 가독성을 높여서 유지보수를 더욱 쉽게 해줍니다.
Sass에서는 두가지 확장자를 지원합니다.
1. .scss
2. .sass
두가지 문법을 살펴보겠습니다.

.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 확장자는 중괄호({})와 세미콜론(;)을 사용하지 않습니다. 보통 .scss 문법이 더 자주 사용됩니다.

utils 함수 분리하기

여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있습니다.
1. src 디렉터리에 styles라는 디렉터리를 생성하고 그 안에 utils.scss 파일을 생성한다.
2. 사용할 컴포넌트에서 cscc 파일을 불러온다. 다른 scss파일을 불러올때는 @import 구문을 사용한다. (ex: @import './styles/utils';)

sass-loader 설정 커스터마이징하기

'./styles/utils'형태로 불러올때 프로젝트 구조가 깊어진 경우 상위폴더로 한참 올라가야합니다. (ex : @import '../../../styles/utils';)
이 문제점을 sass-loader의 설정을 커스터마이징하여 해결할 수 있습니다.

  1. $git add.
  2. $git commit -m 'Commit before yarn eject'
  3. $yarn eject //숨겨진 세부설정을 밖으로 꺼내주는 명령어
  4. $react-scripts eject

해당 작업을 완료하면 프로젝트 디렉터리에 config라는 디렉터리가 생성됩니다.
config안에 webpack.config.js파일에서 "sassRegex"란 키워드를 찾아서 use: 에 있는 "sass-loader"부분을 지우고 뒷부분에 concat을 통해 커스터마이징된 설정을 넣어줍니다.

{
  test: sassRegex,
  exclude: sassModuleRegex,
  use: getStyleLoaders({
    importLoaders: 2,
    sourceMap: isEnvProduction && shouldUseSourceMap
}).concat({
    loader: require.resolve(‘sass-loader‘),
    options: {
      includePaths: [paths.appSrc + ‘/scss폴더‘],
      sourceMap: isEnvProduction && shouldUseSourceMap,}
}),
sideEffects: true
},

설정 파일 저장 후 서버를 껐다가 재시작해줍니다. 이후에는 절대경로로 utils.scss파일을 불러올 수 있습니다.

@import 'utils.scss';

파일 생성 시마다 포함시키는 것을 해결하는 방법은 sass-loader의 additionalData옵션을 설정하면 됩니다.

1.webpack.config.js 의 additionalData필드를 다음과 같이 수정한다.

additionalData: "@import 'utils';",

저장 후 개발 서버를 재시작하면 됩니다.

node_modules에서 라이브러리 불러오기

물결문자(~)를 사용하면 자동으로 node_modules에서 라이브러리 디렉터리를 탐지하여 스타일을 볼러 올 수 있습니다.

@import '~library/styles';

CSS Module

CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉

[파일이름]_[클래스이름]_[해시값]

형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해주는 기술입니다.

classnames

classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리입니다.

$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'

const MyComponent = ({ highlighted, theme }) => (
  <div className={classNames('MyComponent', { highlighted }, theme)}>Hello</div>
);

Sass와 함께 사용하기

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

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

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

styled-components

styled-components 는 현존하는 리액트 CSS-in-JS 관련 라이브러리 중에서 가장 잘나가는 라이브러리입니다. CSS-in-JS 는 이름이 그렇듯, 자바스크립트 파일 안에 CSS 를 작성하는 형태입니다. 해외의 큰 기업 - Atlassian, Reddit, coinbase 등에서도 사용되고 있고, 국내에서도 사용하는곳이 꽤 있습니다 - Channel.io, Huiseoul, Tumblebug 등..

styled-components 의 대체제는 현재 대표적으로 emotion 이 있습니다. 작동 방식은 꽤나 비슷합니다.

$ yarn add styled-components

mport React from 'react';
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 =>
    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;

Tagged 템플릿 리터럴

Tagged 템플릿 리터럴을 사용하면 템플릿 사이사이에 들어가는 자바스크립트 객체나 함수의 원본값을 그대로 추출할 수 있습니다. style-components는 이러한 속성을 사용하여 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해줍니다.

function tagged(...args) {
	console.log(args);
}
tagged`hello ${{foo: 'bar' }} ${() => 'world'}!`

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

스타일링 된 엘리먼트를 만들 땐, 상단에서 styled 를 불러오고 styled.태그명 을 사용하여 구현합니다.

import styled from 'styled-components';

const MyComponent = styled.div`
  font-size: 2rem;
`;

저 자리에 button 이던, input 이던, 원하는걸 넣으시면 됩니다.

하지만, 만약에 보여줘야 할 태그 형식이 유동적이거나, 아니면 특정 컴포넌트에 스타일링을 해야 하는 상황이라면 다음과 같은 형태로 구현 할 수 있습니다.

// 문자열로 styled 의 인자로 전달
const MyInput = styled('input')`
  background: gray;
`
// 아예 컴포넌트 형식의 값을 넣어줌
const StyledLink = styled(Link)`
  color: blue;

스타일에서 props 조회하기

스타일링 한 컴포넌트에 전달하는 props 값을 스타일쪽에서 그대로 사용 하실 수 도 있습니다.

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
`;

props에 따른 조건부 스타일링

일반 CSS 클래스를 사용했더라면 주로 클래스이름으로 조건부 스타일링을 해왔었을텐데요, 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;
  }
`;

반응형 디자인

styled-components 에서 반응형 디자인은 어떻게 하는지 알아봅시다. 일단, 일반 CSS 랑 똑같이 하면 됩니다.

const Box = styled.div`
  /* props 로 넣어준 값을 직접 전달해줄 수 있습니다. */
  background: ${props => props.color || 'blue'};
  padding: 1rem;
  display: flex;
  /* 기본적으로는 1024px 에 가운데 정렬을 하고
    가로 크기가 작아짐에 따라 사이즈를 줄이고
    768px 미만으로 되면 꽉 채웁니다 */
  width: 1024px;
  margin: 0 auto;
  @media (max-width: 1024px) {
    width: 768px;
  }
  @media (max-width: 768px) {
    width: 100%;
  }
`;

이런 작업을 함수화하여 훨씬 더 쉽게 할 수도 있습니다.

import React from 'react';
import styled, { css } from 'styled-components';

const sizes = {
  desktop: 1024,
  tablet: 768
};

// 위에있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어줍니다.
// 참고: https://www.styled-components.com/docs/advanced#media-templates
const media = Object.keys(sizes).reduce((acc, label) => {
  acc[label] = (...args) => css`
    @media (max-width: ${sizes[label] / 16}em) {
      ${css(...args)};
    }
  `;

  return acc;
}, {});

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: 768px;`};
`;

리액트를 다루는 기술 [개정판] (김민준, 길벗출판사) 책을 참고하였습니다.

0개의 댓글