React Custom Component

아기코린이·2022년 6월 30일
2

React

목록 보기
4/5
post-thumbnail

들어가기

이번 블로깅에서는 리액트로 프론트엔드를 구축할 때 구조적으로 코드를 작성하는 방법에 대해 알아보려고 한다.

CDD(Component Driven Development)

CDD는 개발 방법론 중 하나로, bottom up UI를 설계하는 과정을 뜻한다. 디자인과 개발 단계에서부터 재사용할 수 있는 UI 컴포넌트를 미리 디자인하고 같은 UI가 필요한 곳에 공통으로 사용한다.

즉, 레고처럼 조립해 나갈 수 있는 부품 단위로 UI 컴포넌트를 만들어 나가는 개발을 진행할 수 있다.

CSS in JS

CSS의 발전

인터넷이 만들어진 이후 기술의 발달과 함께 사용자들은 다양한 환경에서 인터넷을 사용하기 시작했다. 개발자들의 CSS 작성 방식도 꾸준히 진화한다.

CSS 작업을 효율적으로 하기 위해 구조화된 CSS의 필요성이 대두되었고, CSS를 구조화하는 방법에 대한 연구가 필요해졌다.

문제점들을 해결하기 위해 CSS 전처리기(CSS Preprocessor)라는 개념이 등장했습니다. CSS 전처리기란 CSS가 구조적으로 작성될 수 있게 도움을 주는 도구다.

우리가 흔히 CSS 문서를 작성할 때는 많은 반복적이며 번거로운 작업을 요구한다. 또한, 클래스의 상속 등 점점 CSS 문서는 양이 많아지며 이후 유지관리에 많은 영향을 끼친다. 이런 문제점들을 프로그래밍 개념을 활용하여 해결해 나갈 수 있다.

하지만 이 CSS 전처리기 자체만으로는 웹 서버가 인지하지 못하기 때문에 컴파일링 과정이 필요하다.

CSS 전처리기

CSS 전처리기 중에서 가장 유명한 SASS는 Syntactically Awesome Style Sheets의 약자로 CSS를 확장해 주는 스크립팅 언어다.

CSS를 만들어주는 언어로서 자바스크립트처럼 특정 속성의 값을 변수로 선언하여 필요한 곳에 선언된 변수를 적용할 수도 있고, 반복되는 코드를 한 번의 선언으로 여러 곳에서 재사용할 수 있도록 해 주는 등의 기능을 가졌다.

SCSS로 작성 -> SASS로 컴파일링 -> CSS파일

하지만 얼마 지나지 않아서 SASS가 ‘CSS의 구조화’를 해결해 주는 것의 장점보다 다른 문제들을 더 많이 만들어낸다는 것이 밝혀진다. 전처리기가 내부에서 어떤 작업을 하는지는 알지 못한 채, 단순히 계층 구조를 만들어 내는 것에 의지하게 되었고, 그 결과 컴파일된 CSS의 용량은 어마어마하게 커지게 되다.

이러한 문제를 보완하기 위해 BEM, OOCSS, SMACSS 같은 CSS 방법론이 대두되었다. 각각의 장단점이 있으나 결국 세 방법론 모두 같은 지향점을 가지고 있다.

방법론의 공통 지향점은 다음과 같다.

  • 코드의 재사용
  • 코드의 간결화(유지 보수 용이)
  • 코드의 확장성
  • 코드의 예측성(클래스 명으로 의미 예측)

이런 CSS 방법론들은 같이 일하는 팀 동료들의 팀워크와도 연결되기 때문에 여러 팀원이 함께 작업하는 상황에서 CSS 작성에 있어서 방법들을 규칙으로 정해두는 것은 매우 중요한 요소라고 할 수 있다.

대표적인 CSS 방법론으로는 BEM이 있다. BEM이란 Block, Element, Modifier로 구분하여 클래스명을 작성하는 방법이며, Block, Element, Modifier 각각은 —와 __로 구분한다.

.form__input-name{
		...
}

클래스명은 BEM 방식의 이름을 여러 번 반복하여 재사용할 수 있도록 하며 HTML/CSS/SASS 파일에서도 더 일관된 코딩 구조를 만들어준다.

이러한 방법에도 문제점은 존재한다. 클래스명 선택자가 장황해지고, 이 때문에 마크업이 불필요하게 커지며, 재사용하려고 할 때마다 모든 UI 컴포넌트를 명시적으로 확장해야만 했다.

애플리케이션으로 개발 방향이 진화하면서 컴포넌트 단위의 개발은 캡슐화의 중요성을 불러왔다. 하지만 CSS는 컴포넌트 기반의 방식을 위해 만들어진 적이 한 번도 없었다. 결국 CSS도 컴포넌트 영역으로 불러들이기 위해서 CSS-in-JS가 탄생했고, 이 문제를 정확하게 해결헀다.

CSS-in-JS에는 대표적으로 Styled-Component가 있다.

Styled Components

CSS 코드를 다룰 때 다음과 같은 불편함을 한번쯤은 느껴봤을 것이다.

  • class, id 이름을 짓느라 고민한 적이 있다.
  • CSS 파일 안에서 내가 원하는 부분을 찾기 힘들었다.
  • CSS 파일이 너무 길어져서 파일을 쪼개서 관리해본 적이 있다.
  • 스타일 속성이 겹쳐서 내가 원하는 결과가 나오지 않은 적이 있다.

이런 불편함을 CSS를 컴포넌트화 시킴으로써 해결해주는 라이브러리가 있다. 바로 React 환경에서 사용 가능한 Styled Components다.

기존에 HTML, CSS, JS 파일로 쪼개서 개발하던 방법에서, React 등의 라이브러리의 등장으로 컴포넌트 단위 개발이 주류가 되었지만, CSS는 그렇지 못했다는 점에서 출발한 개념이다.

CSS in JS 라이브러리를 사용하면 CSS도 쉽게 Javascript 안에 넣어줄 수 있으므로, HTML + JS + CSS까지 묶어서 하나의 JS파일 안에서 컴포넌트 단위로 개발할 수 있게 된다. 이런 CSS in JS 라이브러리 중에서 현재 가장 인기 있는 라이브러리가 바로 Styled Components다.

Styled Components 문법

컴포넌트 만들기
Styled Components로 컴포넌트를 만드는 방법은 다음과 같다.

const PurpleButton = styled.button`
  background-color: purple;
  color: white;
`

Styled Components는 ES6의 Templete Literals 문법을 사용한다. 즉, 따옴표가 아닌 백틱(`)을 사용한다.

컴포넌트를 재활용해서 새로운 컴포넌트 만들기
이미 만들어진 컴포넌트를 재활용해서 새로운 컴포넌트를 만들 수도 있다. 컴포넌트를 선언하고 styled() 에 재활용할 컴포넌트를 전달해준 다음, 추가하고 싶은 스타일 속성을 작성해주면 된다.

// 컴포넌트를 재활용할 수 있다.
const BigPurpleButton = styled(PurpleButton)`
  padding: 10px;
  margin-top: 10px;
`

// 재활용한 컴포넌트를 다시 재활용할 수 있다.
const BigPinkButton = styled(BigPurpleButton)`
  background-color: pink;
`

Props 활용하기
Styled Component로 만든 컴포넌트도 React 컴포넌트처럼 props를 내려줄 수 있다. 내려준 props 값에 따라서 컴포넌트를 렌더링하는 것도 가능하다.

Styled Components는 템플릿 리터럴 문법(${ })을 사용하여 JavaScript 코드를 사용할 수 있다. props를 받아오려면 props를 인자로 받는 함수를 만들어 사용하면 된다.

const Button = styled.button`
  background: ${(props) => props.skyblue}
`

...

<Button skyblue={"skyblue"}>Button</Button>

Props로 조건부 렌더링하기

props.color가 없다면 white를, props.color가 있다면 props.color의 값을 그대로 가져와서 스타일 속성 값으로 리턴해주고 있는 것을 볼 수 있다. 그 결과 color 라는 이름으로 받은 props의 값으로 배경색이 지정된 것을 확인할 수 있다.

// 받아온 prop 값을 그대로 이용해 렌더링할 수도 있다.
const Button1 = styled.button`
  background: ${(props) => (props.color ? props.color : "white")}
`

// 다음과 같은 형식으로도 활용할 수 있다.
const Button2 = styled.button`
  background: ${(props) => props.color || "white"}
`

// 두 컴포넌트의 결과는 동일하다.
<Button1>Button1</Button1>
<Button1 color="tomato">Button1</Button1>
<Button2>Button2</Button2>
<Button2 color="turquoise">Button2</Button2>

전역 스타일 설정하기

전역에 스타일을 설정하고 싶을 땐 Styled Components에서 createGlobalStyle 함수를 불러온다.

import { createGlobalStyle } from "styled-components"

그 다음 이 함수를 사용해 CSS 파일에서 작성하듯 설정해주고 싶은 스타일을 작성한다.

const GlobalStyle = createGlobalStyle`
  button {
	padding : 5px;
    margin : 2px;
    border-radius : 5px;
  }
`

이렇게 만들어진 <GlobalStyle> 컴포넌트를 최상위 컴포넌트에서 사용해주면 전역에 <GlobalStyle> 컴포넌트의 스타일이 적용된다.

function App() {
  return (
	<>
	  <GlobalStyle />
	  <Button>Button!!</Button>
	</>
  )
}

hover 적용하기

  const Button1 = styled.button`
  	...
    &:hover{
  	  ...
    }
  `

컴포넌트 UI 개발을 위한 Storybook

CDD가 트렌드로 자리 잡게 되면서 이를 지원하는 도구 중 하나인 Component Explorer가 등장했다. Component Explorer에는 많은 UI 개발 도구가 다양하게 있는데 그중 하나가 Storybook이다.

Storybook

Storybook은 UI 개발 즉, CDD를 하기 위한 도구다. 각각의 컴포넌트들을 따로 볼 수 있게 구성해 주어 한 번에 하나의 컴포넌트에서 작업할 수 있다. 전체 UI를 한눈에 보고 개발할 수 있다.

Storybook은 재사용성을 확대하기 위해 컴포넌트를 문서화하고, 자동으로 컴포넌트를 시각화하여 시뮬레이션할 수 있는 다양한 테스트 상태를 확인할 수 있다. 이를 통해 버그를 사전에 방지할 수 있도록 도와준다. 또한 테스트 및 개발 속도를 향상시키는 장점이 있으며, 애플리케이션 또한 의존성을 걱정하지 않고 빌드할 수 있다.

Storybook은 기본적으로 독립적인 개발 환경에서 실행된다. 개발자는 애플리케이션의 다양한 상황에 구애받지 않고 UI 컴포넌트를 집중적으로 개발할 수 있다.

Storybook에서 지원하는 주요 기능은 다음과 같다.

  • UI 컴포넌트들을 카탈로그화하기
  • 컴포넌트 변화를 Stories로 저장하기
  • 핫 모듈 재 로딩과 같은 개발 툴 경험을 제공하기
  • 리액트를 포함한 다양한 뷰 레이어 지원하기

Storybook 튜토리얼

Storybook으로 컴포넌트들을 문서화해서 관리하는 방법을 알아보자.

먼저 npx create-react-app storybook-practice로 리액트 프로젝트를 만든다.

폴더가 생성되면, 폴더 안에서 npx storybook init을 입력하여 Storybook을 설치한다.

Storybook 설치가 완료되면, /.storybook 폴더와 /src/stories 폴더가 생성된 것을 확인할 수 있다. /.storybook 폴더에는 Storybook 관련 설정 파일이, /src/stories 폴더에는 Storybook 예시 파일들이 들어있습다.

이제 터미널 창에 npm run storybook를 입력하여 Storybook을 실행한다.

Storybook을 실행하면 /src/stories 폴더 안에 있던, Storybook에서 만들어놓은 예시 스토리를 확인할 수 있다. 이렇게 Storybook을 사용하면 애플리케이션을 실행하고 이벤트를 통해 상태를 변경하는 과정을 거치지 않아도 상태 변화에 따른 컴포넌트의 변화를 확인할 수 있다.

스토리 작성
이제 간단한 스토리를 작성해보자. src 폴더 안에 Title.js 파일을 하나 만들고, 다음과 같은 간단한 React 컴포넌트를 하나 만들어 export 해준다.

import React from "react"

const Title = ({title, textColor}) => (
  <h1 style={{color: textColor}}>{title}</h1>
);

export default Title;

그리고 같은 위치인 src 폴더 안에 Title.stories.js 파일을 하나 만든다. /.storybook 안에 있는 Storybook 설정 파일에 의해서 컴포넌트 파일과 똑같은 파일 이름에 .stories를 붙여 파일을 만들면 알아서 스토리로 인식한다. Title.stories.js 는 다음과 같이 작성한다.

import Title from "./Title"

// title : 컴포넌트 이름으로, '/'를 넣어 카테고리화 할 수 있다.
// component : 어떤 컴포넌트를 가져와서 스토리로 만들 것인지 명시한다.
// argTypes : 컴포넌트에 필요한 전달인자의 종류와 타입을 정해준다.
export default {
    title: "Practice/Title", 
    component: Title,
    argTypes: {
        title: { control: "text" },
    }
}

// 템플릿을 만들어준다.
const Template = (args) => <Title {...args} />

// Storybook에서 확인하고 싶은 컴포넌트는 export const로 작성한다.
export const RedTitle = Template.bind({});

// 만들어준 스토리의 전달인자를 작성해준다.
RedTitle.args= {
    title: "Red Title",
    textColor: "red"
}

// 스토리를 하나 더 만든다.
export const BlueTitle = Template.bind({});

// 스토리의 전달인자를 작성해준다.
BlueTitle.args= {
    title: "Blue Title",
    textColor: "blue"
}

Title.stories.js 파일을 저장하고 Storybook을 확인해보자.

title: “Practice/Title” 로 작성해준 코드가 좌측 메뉴에서 카테고리로 적용된 것을 확인할 수 있다.
title.stories.js 안에서 템플릿을 사용해 만든 두 개의 스토리가 Title 안에 들어있는 것을 볼 수 있다.

이번에는 전달인자를 직접 받는 스토리를 만들어보자. 앞서 만들었던 두 스토리 바로 밑에 다음과 같은 스토리를 하나 더 작성한다.

...
export const StorybookTitle = (args) =>{
    return <Title {...args} />
}

이 스토리는 템플릿을 활용하지 않고 바로 전달인자를 받고있다.

이번에 만든 스토리는 전달 인자를 직접 작성해주면 거기에 맞춰 모습이 변하는 것을 볼 수 있다.

이번에는 전달 인자를 직접 받으면서, Styled Components를 사용해서 만든 컴포넌트를 스토리로 만들어보자. 우선, Button.js라는 파일을 하나 만들고 다음과 같은 컴포넌트를 작성한다.

import React from "react"
import styled from "styled-components"

const StyledButton = styled.button`
  background: ${(props) => props.color || "white"};
  width: ${(props) => (props.size === "big" ? "200px" : "100px")};
  height: ${(props) => (props.size === "big" ? "80px" : "40px")};
`

const Button = ({color, size, text}) => (
  <StyledButton color={color} size={size}>{text}</StyledButton>
)

export default Button

Button.stories.js는 다음과 같이 작성해준다.

import Button from "./Button"

export default {
    title: "Practice/Button",
    component: Button,

		// 이번에 작성한 전달인자의 타입은 Storybook을 보고 직접 확인해보세요.
    argTypes: {
        color: { control: 'color'},
        size: { control: { type:'radio', options : ['big', 'small'] }},
        text: { control: 'text'}
    }
};

export const StorybookButton = (args) => (
    <Button {...args}></Button>
)

코드를 저장하고 Storybook을 확인해보자.

useRef

프론트엔드 개발을 하다보면 아래와 같이 DOM의 주소를 참조해야 하는 경우가 생긴다.

  • focus
  • text selection
  • media playback
  • 애니메이션 적용
  • d3.js, greensock 등 DOM 기반 라이브러리 활용

이때, useRef를 사용하면 DOM 노드, 엘리먼트, 그리고 React 컴포넌트 주소값을 참조할 수 있다. 아래 코드는 주소값을 참조하는 방법을 일반화한 코드다.

const 주소값을_담는_그릇 = useRef(참조자료형)

return (
    <div>
      <input ref={주소값을_담는_그릇} type="text" />
    </div>);

이 주소값은 컴포넌트가 re-render 되더라도 바뀌지 않는다. 대부분의 경우 기본 React 문법을 벗어나 useRef를 남용하는 것은 부적절하고, React의 특징이자 장점인 선언형 프로그래밍 원칙과 배치되기 때문에, 조심해서 사용해야 한다.

마치며.

CSS 작업을 진행하면, 항상 클래스명 네이밍에는 시간이 많이 걸렸던것 같다. 이번에 배운 스타일 컴포넌트를 활용하면 네이밍 시간이 줄어들것 같다.

앞으로도 화이팅이다!

profile
아기코린이

0개의 댓글