React.js - 컴포넌트 스타일링

Gyu·2022년 5월 20일
0

React.js

목록 보기
11/20
post-thumbnail
post-custom-banner

리액트 컴포넌트 스타일링

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

일반 CSS

import './App.css' // css 파일 import
  • CSS를 작성할 때 가장 중요한 점은 CSS 클래스 이름을 중복되지 않게 하는 것이다. 왜냐하면 일반 CSS 방식을 사용하면 다른 컴포넌트에도 영향을 미치기 때문이다.
  • CSS 클래스 이름 중복 사용을 방지하는 2가지 방식
    1. CSS 네이밍 규칙을 설정
      • [컴포넌트이름-클래스이름] 형식으로 이름 짓기(예 : .App-header) 등으로 클래스 이름에 컴포넌트 이름을 포함하여 다른 컴포넌트에 실수로 중복되는 클래스를 만들어 사용하는 것을 방지할 수 있다.
    2. CSS Selector
      • CSS Selector를 사용하여 CSS 클래스가 특정 클래스 내부에 있을 때만 스타일 적용하기

Sass

  • create-react-app 2버전부터 별도 추가 설정 없이 Sass를 사용할 수 있다.
  • Sass를 사용하기 위해서는 node-sass라는 라이브러리를 설치해야한다.
  • Sass 팁 : 여러 파일에서 사용될 수 있는 변수 및 믹스인을 다른 파일에 작성한 후 import 하여 사용.
  • // src/styles/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;
    }
    
    // utils.cscc를 불러와서 사용할 scss
    @import 'styles/utils';
    .SassComponent {
      display: flex;
      background: $red;
      .box {
        background: red;
        cursor: pointer;
        transition: all 0.3s ease-in;
    	}
    }

sass-loader 설정 커스터 마이징

  • 프로젝트 디렉토리 구조가 깊어지면 Sass 파일을 불러올 때 상위폴더로 한참 올라가야하는 불편함이 있다. 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하면 Sass파일을 조금 더편하게 불러올 수 있다.
  • create-react-app으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위해 세부 설정을 모두 숨겨 놓았다. 이런 경우를 위해 npm 패키지 내에 포함된 설정파일과 스크립트를 직접 수정할 수 있게 추출(eject)해주는 명령어가 존재한다. 프로젝트 루트 디렉토리에서 npm run eject 라고 입력하면 세부 설정을 밖으로 꺼낼 수 있다. 명령어 입력 전에 모든 파일을 커밋해야한다.
  • 위 명령어 입력 후 생긴 config 폴더 내에 sebpack.config.js 파일을 열어 아래와 같이 수정한다.
    // sassRegex 로 검색하여 아래 부분을 찾는다.
    {
      test: sassRegex,
      exclude: sassModuleRegex,
      use: getStyleLoaders(
    		{
    	    importLoaders: 3,
    	    sourceMap: isEnvProduction && shouldUseSourceMap
    	  },
    		'sass-loader'
    	),
      sideEffects: true
    },
    
    // 위 부분을 아래와 같이 수정
    {
      test: sassRegex,
      exclude: sassModuleRegex,
      use: getStyleLoaders(
    		{
    	    importLoaders: 3,
    	    sourceMap: isEnvProduction && shouldUseSourceMap
    	  }
    	).concat(
    		{
    	    loader: require.resolve('sass-loader'),
    	    options: {
    	      sassOptions: {
    	        includePaths: [paths.appSrc + '/styles']
    	      },
    	      sourceMap: isEnvProduction && shouldUseSourceMap,
    	      prependData: `@import 'utils';` 
    				// 모든 Sass 파일에 utils를 import 하는 옵션
    	    }
    	  }
    	),
      sideEffects: true
    },
  • 수정 후에는 서버를 재시작해야 한다. npm run eject 입력 후 서버가 재시작 되지 않으면, node_modules 디렉토리를 삭제 후 npm install npm start 명령어를 입력해야한다.
  • 위 과정이 끝나면 Sass 파일을 불러올 때 상위 경로 입력 없이 파일 이름만 작성하여 파일을 불러올 수 있다.

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

  • Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 것이다. npm을 통해 설치한 라이브러리는 Sass 파일 내부에서 상대 경로를 사용하여 npde_modules 까지 들어가서 불러와야한다. 이 방식은 매우 번거롭기 때문에 물결 문자를 사용해 자동으로 node_modules에서 라이브러리 디렉토리를 탐지하여 스타일을 불러올 수 있다.
    // 라이브러리는 상대경로로 불러와야 한다.
    @import '../../../node_modules/library/style';
    
    // ~ 을 사용하면 편하게 해당 디렉토리에 접근 가능
    @import '~library/style';

craco를 사용해 절대경로 설정하기

  • 프로젝트의 규모가 커지면 sass 파일 뿐만 아니라 컴포넌트 파일까지 많아지게 되고 그로인해 파일을 불러오는데 많은 불편함이 생기게된다. 이러한 문제를 해결하기 위해서는 cra로 만든 프로젝트에서 웹팩 설정을 변경해줘야한다. 웹팩 설정을 변경하기 위해서는 위에서 말한 eject 명령어를 사용하면 되지만, eject 명령어를 사용한 커스터마이징 방식은 아래의 두 가지 문제점을 야기한다.
    1. 페이스북이 cra를 업데이트 했을 때 프로젝트가 자동으로 업데이트 되지 않기 때문에, 개발자가 직접 모든 configuration을 유지보수해야한다.
    2. One Build Dependency의 장점을 읽게된다.
  • 때문에 eject 명령어 없이 cra의 설정을 바꿀 수 있게 해주는 라이브러리인 craco가 등장했다. craco는 Create React App Configuration Override의 약자로 eject 명령어 없이 쉽게 cra 설정을 바꿀 수 있게 해주는 라이브러리이다.
  • 설치
    npm i --save craco-alias @craco/craco
  • package.json의 script 변경
    "scripts": {
      "start": "craco start",
      "build": "craco build",
      "test": "craco test",
    }
  • root 경로에 tsconfig.paths.json 파일 생성
    // 절대경로 설정 정보를 갖고 있는 파일
    // paths 형식 : path_alias: [실제경로]
    // path_alias로 상대경로가 아닌 절대경로 사용가능
    {
      "compilerOptions": {
        "baseUrl": "./",
        "paths": {
    			"styles/*": ["src/styles/*"], // src/style 내부에 있는 파일은 이제 style로 접근 가능 
          "components/*": ["src/components/*"]
        }
      }
    }
  • tsconfig.json 파일 수정
    {
      "extends": "./tsconfig.paths.json",
      "comilerOptions": {
        // 생략...
      }
      // 생략...
    }
  • root 경로에 croco.config.js 파일 생성
    const CracoAlias = require('craco-alias');
    
    module.exports = {
      plugins: [
        {
          plugin: CracoAlias,
          options: {
            source: 'tsconfig',
            baseUrl: './', // tsconfig.paths.json에 있는 baseUrl 경로값과 동일하게 설정.
            tsConfigPath: 'tsconfig.paths.json',
          },
        },
      ],
    };

CSS Module

  • 리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있다.
  • CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 [파일 이름]_[클래스 이름]_[해시값] 형태의 고윳값으로 만들어 클래스 이름이 중첩되는 현상을 방지한다.
  • create-react-app 2버전부터는 css-loader 설정이 자동으로 되어있기 때문에 따로 설정을 할 필요가 없다. 단지 파일을 .module.css 확장자로 저장하면 CSS Module가 적용된다.
  • CSS Module을 사용하면 import한 컴포넌트에만 스타일이 적용된다. 만약 특정 클래스가 웹페이지 전역적으로 사용된다면 :global 키워드를 앞에 입력하면 된다.
  • /* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/
    
    .wrapper {
      background: black;
      padding: 1rem;
      color: white;
      font-size: 2rem;
    }
    
    /* 글로벌 CSS 를 작성하고 싶다면 */
    :global {
      // :global {} 로 감싸기
      .something {
        font-weight: 800;
        color: aqua;
      }
    }
  • CSS Module이 적용된 스타일 파일을 불러오면 객체를 전달받는데, CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어있다.
  • 고유화한 클래그 이름을 사용하려면 JSX 엘리먼트에 className={style.[클래스 이름]} 형태로 전달해 주면 된다. :global을 사용한 클래스의 경우 기존 방식을 사용하면 된다.
    import React from 'react';
    import styles from './CSSModule.module.scss';
    
    const CSSModule = () => {
      return ( // 클래스 이름을 두 개 이상 적용 시 템플릿 리터럴 사용
        <div className={`${styles.wrapper} ${styles.box}`}>
          안녕하세요, 저는 <span className="something">CSS Module!</span>
        </div>
      );
    };
    
    export default CSSModule;

classnames 사용하기

  • classnames는 여러 클래스를 적용하고, 클래스를 조건부로 설정할 때 유용한 라이브러리다. npm install classnames 로 라이브러리 설치
  • 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
    
    // props 값에 따른 스타일링
    const MyComponent = ({ highlighted, them }) => (
    	<div className={ classNames('MyComponent', { highlighted }, them) }>
    		Hello
    	</div>
    )
  • CSS Module과 classnames를 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 bind 함수를 사용하면 클래스를 넣어줄 때 마다 style.[클래스 이름] 형식으로 코드를 작성하지 않아도 된다. 사전에 미리 style 객체에서 클래스를 받아 온 후 사용하게끔 설정해두고 cx('클래스1', '클래스2') 형식으로 사용할 수 있다.
    import React from 'react';
    import classNames from 'classnames/bind';
    import styles from './CSSModule.module.scss';
    
    const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아오도록 설정하고
    
    const CSSModule = () => {
      return (
        <div className={cx('wrapper', 'inverted')}>
          안녕하세요, 저는 <span className="something">CSS Module!</span>
        </div>
      );
    };
    
    export default CSSModule;
  • Sass또한 .module.scss 로 저장하면 CSS Module를 사용할 수 있다.

styled-components

  • 컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 안에서 스타일링을 선언하는 CSS-in-JS 방식이다. 이와 관련된 라이브러리 중에서는 styled-component와 emotion이 가장 많이 사용되며, 이번 챕터에서는 styled-component에 대해서만 알아보겠다.(이 둘의 사용방식은 꽤 비슷하다.)
  • 설치
    npm i styled-components

Taggeg 템플릿 리터럴

  • styled-component에서는 Tagged 템플릿 리터럴이라는 문법을 사용한다. 그냥 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다는 것이다.
  • 템플릿에 객체를 넣거나 함수를 넣으면 원래 형태를 잃어버린다. 객체는 [object Object]로 변환되고, 함수는 함수 내용이 그대로 문자열화 되어 나타난다.
    `hello ${{foo: 'bar'}} ${() => 'world'}`;
    // 결과 : "hello [object Object] () => 'world'"
  • 만약 아래와 같이 함수를 작성하고, 해당 함수 뒤에 템플릿 리터럴을 넣어준다면, 템플릿 안에 넣은 값을 온전히 추출 할 수 있다.
    function tagged(...args) { console.log(args); }
    
    tagged`hello ${{foo: 'bar'}} ${() => 'world'}`;
  • Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이에 있는 자바스크립트 객체나 함수의 원본 값을 그대로 추출 할 수 있다. styled-component는 이러한 속성을 사용하여 styled-component로 만든 컴포넌트의 props를 스타일 쪽에 쉽게 조회할 수 있도록 해준다.

styled-components를 사용해 엘리먼트에 스타일입히기

import styled from 'styled-components'; // 1. styled 불러오기

// 2. styled.태그명 형식으로 메소드 호출
// 3. Tagged 템플릿 리터럴 문법을 통해 역따옴표 안에 스타일 작성
const MyComponent = stlyed.div`
	font-size: 2rem;
`;
// 위 스타일이 적용된 div로 이뤄진 리액트 엘리먼스 생성

// 4. 생성된 엘리먼트를 아래와 같은 형태로 사용
<MyComponent>Hello</MyComponent>

// div가 아닌 다른 엘리먼트에 스타일을 입히고 싶다면 styled.button, styled.input 형식으로
// styled.태그명을 사용하면된다.
  • 사용해야할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링을 주고 싶다면 아래와 같은 형태로 구현할 수 있다.
    // 태그의 타입을 styled 함수의 인자로 전달
    const MyInput = styled('input')`
    	background: gray;
    `;
    
    // 컴포넌트를 styled 함수의 인자로 전달
    // 이 방식을 사용할 경우 해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정해야 한다.
    const MyComponent = ({className}) => {
    	return <div className={className}>Hello</div>;
    };
    
    const StyledMyComponent = styled(MyComponent)`
    	color: red;
    	font-size: 2rem;
    `;

styled-compoents에서 props 사용하기

  • 스타일에서 props 값 참조
    const Box = styled.div`
    	/* props로 넣어준 값을 직접 전잘해줄 수 있다. */
    	background: ${props => props.color || 'blue'};
    	padding: 1rem;
    	display: flex;
    `;
    
    // JSX에서 사용 시 color 값을 props로 넣어준다.
    <Box color="red"></Box>
  • props에 다른 조건부 스타일링
    import styled, { css } from 'styled-components';
    // 단순 변수의 형태가 아니라 여러 줄의 스타일 구문을 조건부로 설정하는 경우
    // styled-components의 css를 불러와야한다.
    
    const Button = styled.button`
    	background: black;
    	color: white;
    	border-radius: 4px;
    	padding: 0.5rem;
    	
    	/* & 를 사용하여 sass처럼 자기자신 선택 가능 */
    	&:hover { background: rgba(255, 255, 255, 0.9); }
    
    	/* inverted 값이 true일 경우 특정 스타일 부여 */
    	${ props =>
    			props.inverted &&
    			css`
    				background: white;
    				border: 2px solid black;
    				&:hover {
    					background: white;
    					color: black;
    				}
    			`
    	}
    `;
    
    // JSX
    <Button>Hello</Button>
    <Button invertes={true}>태두리만</Button>
    
    // 스타일 코드 여러줄을 props에 따라 넣어주어야 할 경우 styled-components의 css가 필요하다.
    // css 없이 바로 아래와 같이 문자열을 넣어도 작동은한다.
    
    // 하지만 해당 내용이 
    • 스타일 코드 여러줄을 props에 따라 넣어주어야 할 경우 styled-components의 css가 필요하다. css 없이 바로 아래와 같이 문자열을 넣어도 작동은한다.
      ${ props =>
      		props.inverted &&
      		`
      			background: none;
      			border: 2px solid white;
      			&:hover {
      				background: white;
      				color: black;
      			}
      		`
      	}
      `;
    • 그러나 이 방식은 아래의 2가지 문제점이 있다.
      1. 해당 내용이 문자열로 취급되기 때문에 VsCode 확장프로그램에서 신텍스 하이라이팅이 이뤄지지 않음
      2. Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못한다. 즉 해당 부분에서는 props 값을 사용하지 못한다.
    • 만약 조건부 스타일링을 할 때 여러 줄의 코드에서 props를 잠조하지 않는 다면 굳이 css를 불러와서 사용하지 않아도 된다. 하지만 props를 참조한다면 반드시 css로 감싸주어 Tagged 템플릿 리터럴을 사용해야 한다.

반응형 디자인

  • 미디어쿼리 사용
    const Box = styled.div`
    	background: ${props => props.color || 'blue'};
    	padding: 1rem;
    	display: flex;
    	width: 1024px;
    	margin: 0 auto;
    	@media (max-width: 1024px) { width: 768px; }
    	@medis (max-width: 768px) { width: 100%; }
    `;
    • 이 방식은 일반 css와 큰 차이는 없지만, 여러 컴포넌트에서 반복해야한다면 귀찮아질 수 있다. 이 작업을 함수화하여 사용하면 간편하게 반응형 작업을 할 수 있다.
  • 미디어쿼리 유틸함수 만들기
    import styled, { css } from 'styled-components';
    
    const sizes = {
    	desktop: 1024,
    	tablet: 768
    };
    
    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`
    	background: ${props => props.color || 'blue'};
    	padding: 1rem;
    	display: flex;
    	width: 1024px;
    	margin: 0 auto;
    	${media.desktop`width: 768px;`};
    	${media.tablet`width: 100%;`};
    `;
profile
애기 프론트 엔드 개발자
post-custom-banner

0개의 댓글