Component styling

Posted by yunki kim on July 1, 2021

일반 css

 그냥 디폴트로 들어 있는 css이다. 사용법은 가장 초기의 src/App.js, App.css를 보면 된다.

 Css를 사용할때 가장 중요한것은 css클래스를 중복되지 않게 만드는 거다. 이는 다음과 같은 두개의 방식으로 실천할 수 있다.

    1. 규칙있는 이름

      src/App.js초기 로직을 보면 className이 컴포넌트이름-클래스(ex.App-header)형태로 되어 있다. 또 한 BEM naming같은 것을 사용하는 것도 방법이다.

http://getbem.com/naming/

 

BEM — Block Element Modifier

BEM is a highly useful, powerful, and simple naming convention that makes your front-end code easier to read and understand, easier to work with, easier to scale, more robust and explicit, and a lot more strict.

getbem.com

      2. Css selector

        css에서 클래스네임 앞에 .컴포넌트이름을 넣으면 된다

1
2
3
.App .logo{
    height: 10px;
}
cs

 

Sass(Syntactically Awesome Style Sheets)

  문법적으로 매우 멋진 스타일시트란다. 참 이름이 어메이징하다. 아무튼 sass는 css전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 가독성을 높여 유지 보수를 쉽게 해준다.

  Sass는 .scss, .sass 두가지 확장자를 제공한다. .scss는 세미콜론과, {}를 사용하는 반면 .sass는 사용하지 않는다. 또 한 .scss가 기존 css와 문법이 유사하다

1
2
3
4
5
6
7
8
9
10
//.sass
$font-stack: Helvetica, sans-serif
body
    font: 100% $font-stack
 
//.scss
$font-stack: Helvetica, sans-serif
body {
    font: 100% $font-stack;
}
cs

  Sass를 사용하려면 node-sass를 설치해야 한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//sass-component.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 {
  display: flex;
  .box {
    background: red;
    cursor: pointer;
    transition: all 0.3s ease-in;
    &.red { //red class가 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-component.js
import React from 'react';
import './sass-component.scss';
 
const SassComponent = () => {
    return (
      <div className="SassComponent">
          <div className="box red"/>
          <div className="box orange"/>
          <div className="box yellow"/>
          <div className="box green"/>
          <div className="box blue"/>
          <div className="box indigo"/>
          <div className="box violet"/>
      </div>
    );
}
 
export default SassComponent;
cs

utils 함수 분리하기

  여러 파일에서 사용 가능한 변수와 믹스인은 별도의 파일로 분리하여 작성하고 필요할때 불러오면 편하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//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;
}
 
//sass-component.scss
@import './utils';
//...
cs

 

sass-loader 설정 커스터 마이징

  프로젝트 디렉터리가 많아서 구조가 복잡하다면 상대 경로로 파일을 불러오는 것은 좋은 생각이 아니다. 이는 Sass를 처리하는 sass-loader의 설정을 커스터마이징하여 해결할 수 있다. 리액트 앱을 처음 만든 후 프로젝트는 구조의 복잡도를 낮추기 위해 세부 설정을 숨긴다. 이를 커스터 마이징 할려면 yarn eject 또는 npm run eject를 통해 세부 설정을 밖으로 꺼내야 한다. 또 한 이 명령어를 실행하기 전에 모든 파일들이 깃에 커밋되어 있어야 한다.

   npm run eject를 실행하면 config라는 디렉터리가 생긴다. config/webpack.config.js에서 sassRegex라는 키워드를 찾자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    test: sassRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders(
        {
            importLoaders: 3,
            sourceMap: isEnvProduction
                ? shouldUseSourceMap
                : isEnvDevelopment,
        },
            'sass-loader'
    ),
    sideEffects:ture,
},
cs

위 부분을 다음과 같이 수정하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 {
    test: sassRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders(
        {
            importLoaders: 3,
            sourceMap: isEnvProduction
                ? shouldUseSourceMap
                : isEnvDevelopment,
        }).concat({
            loader: require.resolve('sass-loader'),
            options: {
                sassOptions: {
                    includePaths: [path.appSrc + '/style']
                },
                sourceMap: isEnvDevelopment && shouldUseSourceMap,
            }
        }
    ),
    // Don't consider CSS imports dead code even if the
    // containing package claims to have no side effects.
    // Remove this when webpack adds a warning or an error for this.
    // See https://github.com/webpack/webpack/issues/6571
    sideEffects: true,
},
cs

위와 같이 설정하게 되면 scss파일의 경로가 어디에 있더라고 styles 디렉터리 기준 절대 경로로 작동하게 된다. 만약 특정 파일을 디폴트로 넣어주고 싶다면 위 코드에서 sourceMap아래 줄에 다음과 같은 코드를 넣으면 된다.

1
prependData: `@import 'utils';`;
cs

  만약 다음과 같은 에러가 발생한다면 이는 sass-loader의 버전차이로 인해 발생한 오류이니 prependData대신 additionalData를 사용하면 된다 

1
2
3
4
5
//Error: 
//options has an unknown property 'prependData'. These properties are valid:
//   object { implementation?, sassOptions?, additionalData?, sourceMap?, webpackImporter? }
//solution:
additionalData: `@import 'utils';`;
cs

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

  Sass는 라이브러리를 쉽게 불러와 사용할 수 있다. 또 한 node_modules에서 라이브러리를 불러올때는 맨 앞에 '~'를 추가하는 것으로 바로 node_modules에 접근할 수 있다. 

  반응형 디자인을 쉽게 만들어 주는 include-media, open-color를 npm으로 설치한 후 다음과 같이 하면 된다. 아래의 코드는 화면 크기가 768px보다 작아지면 배경 색을 바꾼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//utils.css
@import '~include-media/dist/include-media';
@import '~open-color/open-color';
 
//sass-component.scss
.SassComponent {
  display: flex;
  background: $oc-gray-2;
  @include media('<768px') {
    background: $oc-gray-9;
  }
  .box {
    //..
  }
}
cs

CSS module

 CSS module은 css를 불러와서 사용할 때 이름을 고유값 [파일 이름]_[클래스 이름]_[해시값]형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해준다. 따라서 흔한 이름을 사용한다고 해도 문제가 되지 않는다. 해당 클래스는 방금 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동 된다. '.module.css'라는 확장자를 사용하면 css module을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//css_module.module.css
.wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
}
 
/*global css*/
:global .something {
    font-weight: 800;
    color: aqua;
}
 
//css_module.js
import React from 'react';
import styles from './css_module.module.css';
 
const CssModule = () => {
    return (
        <div className={styles.wrapper}>
            Hi, I am <span className="something">CSS module</span>
        </div>
    )
}
 
export default CssModule;
 
//CSS Module을 사용한 클래스 이름을 두개 이상 적용시
  <div className={`${styles.wrapper} ${styles.inverted}`}></div>
cs

  scss를 사용할 때는 '.modules.css'대신에 '.modules.scss'를 사용하면 scss를 css module로 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*css_module_scss_module.scss*/
.wrapper 
{
  background: black;
  padding: 1rem;
  color: white;
  font-size: 2rem;
  &.inverted {
    color: black;
    background: white;
    border: 1px solid black;
  }
}
 
:global {
  .something {
    font-weight: 800;
    color: aqua;
  }
}
cs

  모듈이 아닌 일반 css, scss파일에서도 :local을 사용해 css module을 정의할 수 있다.

1
2
3
4
5
6
7
8
9
:local .wrapper {
    /*style*/
}
 
:local {
    .wrapper {
        /*style*/
    }
}
cs

 

Styled-components

  컴포넌트 스타일링은 js 파일 안에서도 할 수 있다. 이를 CSS-in-JS라고 한다. 이를 위한 라이브러리 중 styled-components라는 라이브러리를 가장 많이 사용한다.

  CSS-in-JS를 사용하게 되면 별도의 스타일 관련 파일을 만들지 않아도 되기 때문에 큰 이점이 생긴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//styled_component.js
import
 React from 'react';
import styled, {css} from 'styled-components';

//div에 스타일링을 해서 Box라는 엘리먼트로 사용한다
const Box = styled.div `
  //props로 넣은 값을 직접 전달 할 수 있다
//여기에서의 props.color는 아래 Box태그의 color이다.
  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(2552552550.9);
  }
  
  //inverted값이 참일때 특정 스타일을 부여한다
  ${(props) =>
//styled-components를 사용하면 조건부 스타일링을
//간단하게 props로 처리할 수 있다.
      props.inverted && 
      css `
        background: none;
        border: 2px solid white;
        color: white;
        &:hover {
          background: white;
          color: black;
        }
      `
  };
  & + button {
    margin-left: 1rem;
  }
`;
 
const StyledComponent = () => (
//위에서 스타일링을 한 div
    <Box color={"black"}>
        <Button>Hello</Button>
        <Button inverted={false}>테두리만</Button>
    </Box>
);
 
export default StyledComponent;
 
cs
 
 
 

  위 코드 중 아래의 코드의 경우 css가 아닌 문자열로 주어도 동작은 가능 하다. 하지만 문자열로 처리를 할 경우 vs code확장 프로그램에서 syntax highlighting이 제대로 안된다. 또 한 함수를 받아와 사용하지 못해서 해당 부분에서는 props값을 사용하지 못한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
${(props) =>
      //styled-components를 사용하면 조건부 스타일링을 
      //간단하게 props로 처리할 수 있다.
      props.inverted && 
      css `
        background: none;
        border: 2px solid white;
        color: white;
        &:hover {
          background: white;
          color: black;
        }
      `
};
 
//위 코드 대신
${(props) =>
      //styled-components를 사용하면 조건부 스타일링을 
      //간단하게 props로 처리할 수 있다.
      props.inverted && 
      //
      `
        background: none;
        border: 2px solid white;
        color: white;
        &:hover {
          background: white;
          color: black;
        }
      `
};
cs

 스타일링된 엘리먼트는 위의 Box와 같은 방법으로 만들어가 아래에 있는 방식으로 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
//태그의 타입을 styled 함수의 인자로 전달한다
const MyInput = styled('input')`
    background: gray;
`
 
//아예 컴포넌트 형식의 값을 넣어 준다
const StyledLink = styled(Link)`
    color: blue;
`
 
cs

반응형 디자인

  styled-components에서 반응형 디자인을 하기 위해서는 media 쿼리를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//위 코드의 Box부분을 다음과 같이 바꾼다
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%;
  }
`;
cs

  하지만 이와 같은 방식은 여러 컴포넌트가 있을때 귀찮을 수 있다. 이때는 다음과 같이 styled-components에서 제공하는 유틸함수를 사용하면 media 쿼리가 자동으로 만들어 진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React from 'react';
import styled, {css} from 'styled-components';
 
const sizes = {
    desktop: 1024,
    tablet: 768,
};
 
//위에 있는 size객체에 따라 자동으로 media query 함수를 만들어 준다
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: 100%;`};
`;
 
 //...
);
 
export default StyledComponent;
 
cs

https://styled-components.com/docs/advanced#media-templates

 

styled-components: Advanced Usage

Theming, refs, Security, Existing CSS, Tagged Template Literals, Server-Side Rendering and Style Objects

styled-components.com