컴포넌트를 어떻게 나눌 것인가?
현실에서 일어나는 일들
우리는 프로덕션을 개발하며 수많은 변경을 마주하게 된다. 이에 대해 현명한 대처가 필요하다. 이러한 이유는 뭘까?
- 현재의 제품이 완벽하지 않다.
- 사용자 입장에서 불편함이 있다.
만일 프로덕션이 변경될 일이 없다면?
- 현재의 제품이 완벽하다.
- 사용자 입장에서 불편함이 없다.
사실 제품이 변화하는 것은 놓쳤던 고객의 니즈를 발견하고 제품을 성장시키는 과정이다. 그리고 이러한 변경에 대응하는 것은 쉬운 일이 아니다. (특히 설계가 제대로 되지 않은 소프트웨어에서는 더더욱 그렇다.)
제품이 만들어지는 흐름
- 작은 역할을 하는 여러 개의 컴포넌트를 만든다
- 기능을 만들기 위해 컴포넌트를 합친다.
- 컴포넌트가 너무 커지면 적당히 분리한다.
위의 흐름은 틀리지 않았지만, 우리는 몇가지 전략을 따르며 '적당히' 에 대해 합리적이고 구체적인 기준을 제시할 수 있어야 한다.
변경에 유연한 컴포넌트는 어떻게 만드는가?
- Headless 기반의 추상화하기
- 상대적으로 잘 변하는 것과 변하지 않는 것을 구분하자.
- 한 가지 역할만 하기
- 한 가지 역할만 하는 컴포넌트를 조합하여 기능을 만들자.
- 도메인 분리하기
- 도메인을 포함하는 컴포넌트와 그렇지 않은 컴포넌트를 구분하자.
Headless 기반의 추상화 알아보기: 프론트엔드에서 컴포넌트의 역할 알아보기
크게 2가지로 나눌 수 있다.
(UI 작업) HTML 코드와 DOM 요소들이 주로 이용되는 부분
UI 를 표기하는 역할
- 받은 데이터를 UI 를 통해 사용자에게 친절한 형태로 보여준다.
- DOM 요소들과 CSS 가 주로 이용된다.
(데이터 작업) 자바스크립트 코드가 주로 이용되는 부분
데이터를 불러오는 역할
- 초기에 표기할 데이터를 요청하고 받는다.
fetch
,ajax
,axios
등을 이용한다.- 상태를 관리하며 상호작용할 것이라면, 자바스크립트에 값을 잘 저장해놓는 것이 유리하고 일회용으로 보여줄 것이라면 단순히 UI 와 한번 매핑만 해도 된다.
로직을 통해 상호작용하는 역할
onclick
,onsubmit
등에 작성된 로직을 이용해 사용자와 상호작용한다.
데이터 작업에만 집중해 모듈화한다면?
- 이렇게 데이터에만 집중해 모듈화하는 것을
Headless
라고 한다.- 비주얼적으로 보여주는 것은 없지만, 실체는 있기 때문에 이런 표현을 쓰는 것 같다.
직접 작성해본 예제 코드
pages/index.tsx
의 코드
import type { NextPage } from "next";
import Button from "../components/Button";
import usePressButton from "../hooks/usePressButton";
const Home: NextPage = () => {
return (
<>
<Button usePressButton={usePressButton()} />
</>
);
};
export default Home;
- 오직 페이지의 큰그림만 담당하고 있다.
components/Button.tsx
의 코드
import { usePressButtonInterface } from "../hooks/usePressButton";
interface Props {
usePressButton: usePressButtonInterface;
}
export default function Button({ usePressButton }: Props) {
return (
<button {...usePressButton} style={{ fontSize: "15px", color: "red" }}>
버튼!
</button>
);
}
index.tsx
페이지에서 버튼을 나타내는데 사용될 컴포넌트로 스타일에 대한 정보를 담고 있다.
hooks/usePressButton.tsx
의 코드
import { MouseEvent } from "react";
export interface usePressButtonInterface {
onClick: (e: MouseEvent) => void;
}
export default function usePressButton(): usePressButtonInterface {
const onClick = (e: MouseEvent): void => {
console.log("버튼을 클릭했을 때 실행될 로직");
};
return { onClick };
}
index.tsx
페이지에서 버튼의 로직에 대한 내용만을 추상화한 hooks 이다.
액션 아이템
우리는 그러면 실무에서 무엇을 해볼 수 있는가?
일반화 시켜보고 분리해보자.
- 우리는 우리에게 가장 친숙한 간단한 단어로 되어있는 코드를 볼 때 가장 이해도가 높아진다.
- 사용자 프로필 수정을 위해
ModifyButtonClick
이라는 모든 역할이 뭉쳐진 컴포넌트가 존재한다면, 각 역할별로 쪼개고 일반화하여 재사용할 수 있는 컴포넌트로 분리할 수 있는지 확인해보자. - 특정 도메인에 대한 단어가 빠졌을 때, 컴포넌트는 진정 재사용될 기회를 얻게 된다.
무언가 만들기 전에 인터페이스를 먼저 설계해보자.
- 이미 해당 컴포넌트가 있고, 나는 단순히 사용자가 된 것처럼 생각해보며 어떤 기능이 있을지 상상해보고 만들어보자.
- 이 컴포넌트의 의도는 무엇인가?
- 이 컴포넌트의 기능은 무엇인가?
- 이 컴포넌트가 가지는 데이터는 무엇인가?
- 이 컴포넌트는 어떻게 표현되어야 하는가?
컴포넌트를 나누기 전에 컴포넌트를 나누는 이유를 먼저 생각해보자.
- 결국 나누고 재사용하는 것은 거대한 애플리케이션의 복잡도를 낮추기 위함이다.
- 정말로 복잡도가 낮아지는가?
- 정말로 재사용이 가능해지는가?
- 꼭 분리해야 그 의도가 명확해지는가?
잘 만든 컴포넌트의 이점
- 이미 해결된 비즈니스 문제를 다시 한번 해결할 필요 없이 재사용을 통해서 해결할 수 있게 된다.
- Single Source of Truth 가 지켜지고 매우 작은 단위로 나뉘어져 테스트하기 쉽고 변경에 유연해진다.
참고 강연 링크
https://www.youtube.com/watch?v=fR8tsJ2r7Eg&ab_channel=%ED%86%A0%EC%8A%A4
'프론트엔드 > React' 카테고리의 다른 글
React Effect Hook 이란? (0) | 2022.10.19 |
---|---|
리액트 컴포넌트 밖 변수 선언의 의미 (Feat. 리액트에서 절대 하면 안되는 것 1가지) (0) | 2022.10.14 |
React Hook Form 이 해결하는 문제들과 사용법 (0) | 2022.07.02 |
React Hook Form 소개 (0) | 2022.07.02 |