본문 바로가기

FrontEnd/React

[React] 리액트 컴포넌트의 CSS 스타일링

컴포넌트 스타일링

 컴포넌트 스타일링이란, React 컴포넌트의 UI(사용자 인터페이스)에 적용할 CSS를 정의하는 방법을 의미한다.
React는 컴포넌트 기반 라이브러리이므로, 각 컴포넌트마다 고유한 스타일을 적용할 수 있다.

 

 부트스트랩 사용해 보기

  부트스트랩(Bootstrap)은 웹 개발에서 널리 사용되는 프론트엔드 프레임워크이다.  주로 웹 사이트의 디자인과 반응형 웹   (Responsive Web)을 쉽게 구현할 수 있도록 돕는 도구이다.  여러가지 UI 구성 요소(예: 버튼, 네비게이션 바, 가드 등)와 반응형   그리드 시스템을 제공한다.

 부트스트랩을 사용해 리액트 컴포넌트를 사용하기 위해 부트스트랩 홈페이지의 CDN links의 CSS경로를 복사한다.

https://getbootstrap.com/docs/5.3/getting-started/introduction

 

 그 후 public 디렉터리의 index.html파일에 붙여 넣는다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />

    <meta name="description" content="Web site created using create-react-app" />

    <!-- Bootstrap CSS -->
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
      crossorigin="anonymous"
    />

    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    ... 생략 ...

 

 부트스트랩 컴포넌트 구현하기

  부트스트랩 사이트의 Forms > Overview의 소스를 복사한다.

https://getbootstrap.kr/docs/5.0/forms/overview/


  해당 소스를 React 소스에 붙여 넣을 경우 class, for등에서 빨간 줄이 표시되어 오류간 난걸로 나타난다.

JSX에서 class, for 등에서 오류가 표시되는 것을 확인할 수 있다.

 

 리액트 className과 htmlFor 속성

  JSX문은 React.createElement 함수 호출 코드를 간결하게 하려고 고안한 것이며 JSX문을 사용 시 React.createElement 함수로 전환횐다.  하지만 전환 과정에서 자바 스크립트(타입스크립트) 키워드인 class, for와 혼란(충돌)이 일어나기 때문에 JSX에서는 className, htmlFor라는 속성명을 사용한다.

export default function CopyMe() {
  return <form>
  <div className="mb-3">
    <label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
    <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" />
    <div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
  </div>
  <div className="mb-3">
    <label htmlFor="exampleInputPassword1" className="form-label">Password</label>
    <input type="password" className="form-control" id="exampleInputPassword1" />
  </div>
  <div className="mb-3 form-check">
    <input type="checkbox" className="form-check-input" id="exampleCheck1" />
    <label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
  </div>
  <button type="submit" className="btn btn-primary">Submit</button>
</form>
}

className, htmlFor로 변경한 JSX 결과물

 웹팩과 CSS 파일 임포트

  리액트 프로젝트가 내부적으로 사용하는 웹팩은 .css파일을 파일을 좀 더 쉽게 사용할 수 있게 해준다.  이미지와 CSS, 자바스크립트(타입스크립트) 코드가 혼합된 프로젝트를 서비스하기 좋다.  또한 import문 형태로 CSS파일을 <link> 태그 없이 이용할 수 있게 해준다. 

import './index.css';

 

 CSS 기본 구문

  CSS(Cascading Style Sheet)는 HTML과 함께 웹을 구성하는 기본 프로그래밍 요소이다.  CSS는 선택자와 선언부로 구성된다.  선언부에는 CSS 속성이름과 값이 포함된다.  2개 이상 단어로 된 속성(font-size)는 소문자 스네이크 표기법(snake case)을 사용한다. 

  스타일의 속성 값은 대부분 작은따옴표 없이 사용하지만 'times New Roman' 처럼 속성 값이 여러개인 단어일 땐 작은 따옴표로 감싸준다.  또한, 속성값이 여러 개라면 각 값을 쉼표(,)로 구분한다.

h1 {
    color:red; 
    font-size:15px;
}

 

 선택자(selector)란?

  CSS 선택자(selector)는 CSS 규칙을 적용한 HTML 요소를 정의한다.  CSS 선택자의 종류중 대표적인 세 가지는 '전체 선택자', '유형 선택자', '클래스 선택자' 이다.

  • 전체 선택자(universal selector) : HTML 모든 태그를 선택하는 용도로 사용된다.  *를 전체 선택자라고 한다.
    * {
        box-sizing : border-box
    }
  • 유형 선택자(type selector) : HTML 태그 이름을 사용하는 선택자를 유형 선택자라고 한다.
    h1, h2 {
        font-family: Roboto, 'times New roman', sans-serif;
    }
  • 클래스 선택자(class selector) : '.클래스명'으로 사용하는 선택자를 클래스 선택자라고 한다.
    .wrapper {
        background-color: blue;
    }

 @import 규칙으로 아이콘 사용하기

  CSS는 @import, @media 등 @으로 시작되는 구문을 제공하며 해당 구문을 앳 규칙(at rules)이라고 한다.

 CSS의 @import 규칙은 <link rel='stylesheet' href> 대신 .css 파일에서 다른 .css 파일을 사용하고자 할 때 적용한다.

@import url('https://fonts.googleapis.com/icon?family=Material+Icons');

.material-icons {
  font-family : 'Material Icons';
  display : inline-block;
}

 

  구글 머티리얼 아이콘을 사용하기 위한 JSX Icon 컴포넌트를 작성한다.

export default function Icon() {
  return (
    <div>
      <h3>Icon</h3>
      <span className="material-icons">home</span>
      <span className="material-icons">check_circle_outline</span>
    </div>
  )
}

구글 머티리얼 아이콘 사용 결과

 style 속성을 사용한 인라인 스타일링

  HTML 태그 내의 속성으로 style이라는 속성에 문자열로 된 CSS코드를 설정할 수 있으며 해당 방식을 인라인 방식이라고 한다.   다만 리액트 컴포넌트에서는 style속성에 설정할 값이 객체(스타일 객체)여야 한다.

// {{color: 'blue'}} 은 스타일 객체이다.
export default function Style() {
  return (
  <div>
    <h3>Style</h3>
    <span className="material-icons" style={{color: 'blue'}}>home</span>
    <span className="material-icons" style={{fontSize:'50px', color: 'red'}}>check_circle_outline</span>
  </div>)
}

스타일 객체를 적용한 구글 머티리얼 아이콘 결과

 Node.js 패키지 방식으로 아이콘 사용하기

  @import 규칙은 웹에 안전한 글꼴, 즉 웹 안전 글꼴(web safe font)을 사용해야 하는 제약이 존재한다. 

 웹 안전 글꼴이란 데스크톱, 모바일 등 모든장치의 브라우저에서 적용한 글꼴을 의미하며 사용자 컴퓨터에 설치되지 않은 때에도   항상 올바르게 표시되는 글꼴을 말한다.   구글이 제공하는 모든 글꼴은 웹 안전 글꼴이므로 @import규칙을 적용할 수 있다.

 

 fontsource(fontsource.org)는 구글 글꼴과 같은 오픈소스 웹 안전 글꼴을 Node.js 패키지 형태로 설치해 준다.

 

 머터리얼 아이콘 설치하기

> npm i @fontsource/material-icons

added 1 package, and audited 1348 packages in 10s

263 packages are looking for funding
  run `npm fund` for details

8 vulnerabilities (2 moderate, 6 high)

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

 

 머티리얼 아이콘 사용하기

// index.tsx에 설치한 패키지 import하기
import '@fontsource/material-icons'

 

 Icon 사용자 컴포넌트 구현하기

// Icon.tsx
import type {CSSProperties, FC} from 'react'

// 
export type IconProps = {
  name: string
  style?: CSSProperties // style은 선택 속성 '?'(optional property)이다.
}

// 잔여연산자를 적용한 ...props
// 매개변수 props는 변수이므로 비구조화 할당구문을 적용할 수 있다. ({name, ...props})
export const Icon: FC<IconProps> = ({name, ...props}) => {
  return (
    <span {...props} className="material-icons">
      {name}
    </span>
  )
}
// index.ts
export * from './Icon'
// Icon.tsx

import {Icon} from '../components/'
export default function UsingIcon() {
  return (
    <div>
      <h3>UsingIcon</h3>
      <Icon name="home" style={{color: 'blue'}} />
      <Icon name="check_circle_outline" style={{fontSize: '50px', color: 'red'}} />
    </div>
  )
}

 

 import { Icon } from '../components' 경로만 적었는데 대체 왜 indes.ts에 접근이 가능한걸까?
  해당 파일에 접근할 수 있는 이유는 Node.js의 모듈 해석 방식 때문이다.  일반적으로, 디렉토리 경로를 지정할 때 파일 이름을 생략하면, Node.js와 리액트 빌드 시스템(Webpack, Vite 등)이 해당 디렉토리 내에서 index.ts 또는 index.js 파일을 자동으로 찾는다. 이는 기본적으로 디렉토리의 기본 파일을 찾는 방식인데, index.ts는 특정 디렉토리 내에서 해당 디렉토리의 "엔트리 포인트" 역할을 한다.

 

 클래스 선택자 사용하기

  우선 CSS 클래스 선택자 2개를 .css파일에 선언한다.  그후 tsx파일을 생성해 해당 className을 적용한다.

// App.css
.text-blue {
  color: blue;
}
.text-red {
  color: red;
}

 

  하단의 소스가 동작하려면, Icon 컴포넌트에 className속성을 추가해야 한다.  다만 앞서 사용했던 속성 추가하는 방식과 다르게 조금 번거로운 부분이 존재해 리액트 프레임워크는 한꺼번에 특정 HTML 요소의 속성들을 추가할 수 있게 해주는 DetailedHTMLProps와 HTMLAttributes 타입을 제공한다.

// UsingIconWithCSSClass.tsx

import {Icon} from '../components'

export default function UsingIconWithCSSClass() {
  return (
    <div>
      <h3>UsingIconWithCSSClass</h3>
      <Icon name="home" className="text-blue" />
      <Icon name="check_circle_outline" className="text-red" style={{fontSize: '50px'}} />
    </div>
  )
}

 

 리액트가 제공하는 DetailedHTMLProps 와 HTMLAttributes 타입

  하단의 소스는 span 요소의 정의이다.

span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>

 

  위 소스를 좀더 읽기 쉽게 작성한 예이며, ReactSpanProps는 <span> 요소의 모든 속성을 표현하는 타입이다.

import type {FC, DetailedHTMLProps, HTMLAttributes} from 'react'
type ReactSpanProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>

 

 타입 스크립트의 교집합 타입 구문

  자바와 유사한 형태로 IconProps 인터페이스를 만들 수 있다.

// export : IconPrpos 인터페이스를 다른 파일에서 import할 수 있게 공개한다.
// interface IconProps : IconProps라는 인터페이스를 정의한다.
// extends ReactSpanProps : IconProps가 ReactSpanProps를 상속한다는 의미이다.
// 즉 ReactSpanProps에 포함되어 있는 className, id, style 등의 속성을 사용할 수 있다.
export interface IconProps extends ReactSpanProps {
    name: string
}

  

  반면 함수형 언어에서는 | 기호를 쓰는 합집합 타입(union type)과 & 기호를 쓰는 교집합 타입(intersection type) 구문을 제공한다.  A|B는 'A 또는 B인 타입', A&B는 'A이고 B인타입' 이다.

// IconProps 타입은 ReactSpanProps타입이면서 {name:string} 타입이다. (교집합)
export type IconProps = ReactSpanProps & {
	name: string
}

 

 즉, IconProps가 이 2가지 타입의 교집합 타입이므로 {name: string} 타입의 name속성과 ReactSpanProps 타입 props를 잔여 연산자 구문으로 각기 얻을 수 있다.

export const Icon: FC<IconProps> = ({name, ...props}) => {
... 생략 ...

 

 타입 스크립트에서 매개변수 이름 바꾸기

  타입스크립트는 매개변수 이름 뒤에 콜론(:)을 붙여 매개변수의 이름을 다른 이름으로 바꿀 수 있다(리네이밍).

export const Icon: FC<IconProps> = ({name, className: _className, ...props}) => {
...  생략 ...

 

 완성된 Icon 컴포넌드

import type {FC, DetailsHTMLAttributes, HTMLAttributes, DetailedHTMLProps} from 'react'

type ReactSpanProps = DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>

export type IconProps = ReactSpanProps & {
  name: string
}

// prettier-ignore
export const Icon: FC<IconProps> = ({name, className: _className, ...props}) => {
  const className = ['material-icons', _className].join(' ')
  return (
    <span {...props} className={className}>
      {name}
    </span>
  )
}