React를 프로젝트와 컴포넌트 단위에서 설계하기

2023. 12. 28. 20:07Front-end/React.js

반응형

Background

React로 앱을 개발하다보면 점차 프로젝트의 디렉토리와 파일 구조, 그리고 컴포넌트가 복잡해집니다. 여러가지 기능을 구현하고 수정하면서 코드도 복잡해지는데 이를 해결할 수 있는 방법이 있을까요?

이 문제를 해결하고자 수십개의 글을 읽어봤지만 내린 결론은 '정답이 없다'입니다. 그 대신 방향성은 어느정도 정해져 있습니다.

 

React 공식홈페이지에서 소개하는 React 설계방법은 다음과 같습니다.

 

1. 확장성있고 재사용성 있는 코드를 작성하기 

2. 관심사에 따라서 코드를 분리하고 단일 책임을 가지는 컴포넌트를 만들기

3. 외부에 제어를 위임시키는 것을 고려하기

 

이는 작성된 코드를 유지보수하기 쉽고 기능 추가나 변경에 쉽게 대응할 수 있도록 최대한 객체지향을 추구하라는 의미로 들립니다.


 

React의 프로젝트 구조

정답이 없지만 다음과 같은 기준으로 나누면 좋다고 생각합니다.

- pages: 라우트할 페이지

- components: 재사용이 가능한 컴포넌트들 

- styles: styled-component 등의 스타일 파일

- states: 상태관리 폴더

- hooks: 로직을 관리할 custom hook 폴더

 

여기에 기반한 대략적인 프로젝트 구조는 다음과 같습니다.

PROJECT
├── .vscode/
├── node_modules/
├── package.json
├── package-lock.json
├── .eslintrc
├── .prettierrc
├── .gitignore
├── README.md
│
├── public/                        
│   └── images/                          // 이미지 파일 폴더
│
└── src/
	├── index.js
 	├── Router.js                 
    ├── components/                      // 공통 컴포넌트 폴더
    │   ├── ComponentA.tsx               // 공통 컴포넌트 A
    │   └── ComponentB.tsx               // 공통 컴포넌트 B
    │
    ├── pages/                           // 라우트 페이지 폴더
    │   ├── Index.tsx                    // 페이지
    │   └── SubPage.tsx                  // 하위 페이지
    │ 
    ├── styles/               	         // styled-component 스타일 폴더
    │   ├── GlobalStyle.tsx
    │   └── theme.tsx
    │
    ├── states/                          // 상태 관리 폴더
    │   ├── atoms.ts                     // recoil atom
    │   └── selectors.ts                 // recoil selectors
    │ 
    └── hooks/                           // custom hook 폴더

 

 

 

React의 컴포넌트 설계

컴포넌트 설계도 정답은 없습니다. 그러나 여러 문서들을 읽고 나름 공통되는 내용을 정리해보면 다음과 같습니다. 

내용물(로직)은 custom hook으로, 껍데기(UI)는 별도의 컴포넌트로 분리하고 상태를 주입해준다.

 

custom hook

React의 함수형 컴포넌트에는 useState, useEffect 등의 hook이 있습니다. 이러한 hook들처럼 상태 또는 라이프사이클에 맞게 나만의 로직을 실행하고자 한다면 custom hook을 만들게 됩니다. 이 custom hook은 재사용이 가능하다는 점이 특징이며, 컴포넌트 설계 시 공통되는 로직을 custom hook으로 분리하여 재사용하는 점이 효율적입니다.

 

상태(state)

여기서 언급하는 '상태(state)'라는 개념은 동적인 웹사이트를 만드는데 핵심이 됩니다. 사용자가 UI와 상호작용하려면 데이터 모델을 수정할 수 있는 방법이 있어야 하고 이것이 바로 state입니다.

state는 중복을 최소화 해야하고 계산할 수 있는 값은 state에서 가져오는게 아니라 따로 계산되도록 해야합니다. 예를 들어, Todo 리스트를 만든다면 Todo 아이템을 저장하는 배열을 만들지만 이 아이템의 개수를 구하는 state는 별도로 만들지 않고 렌더링 시에 연산하는 식으로 값을 만들 수 있습니다.

또한 어떤 데이터가 state가 되어야 할지 결정해야 합니다. state는 동적으로 상호작용하는 웹을 만들고자 존재하는 것이기 때문에, 정적인 데이터를 굳이 state로 관리할 필요는 없기 때문입니다.

 

state는 다음과 같은 기준으로 정합니다.

 

1. 부모로부터 props를 통해서 전달되지 않고,

2. 시간이 지나면 변하고,

3. 컴포넌트 내부의 다른 state나 props를 가지고 계산이 불가능한 데이터

 

state를 정했다면, 어떤 컴포넌트가 state를 소유할 지 정해야 합니다.

state를 소유할 컴포넌트는 다음과 같은 과정으로 정합니다.

 

1. state를 기반으로 렌더링 하는 모든 컴포넌트 중에서, *공통 소유 컴포넌트를 찾습니다.

* 특정 state가 있어야 하는 모든 컴포넌트들의 상위에 있는 하나의 컴포넌트

2. 공통 혹은 더 상위에 있는 컴포넌트가 state를 가져야 합니다.

3. state를 소유할 적절한 컴포넌트를 찾지 못하였다면, state를 소유하는 컴포넌트를 하나 만들어서 공통 오너 컴포넌트의 상위 계층에 추가합니다.

 

[참고 자료]

 

[리액트 주요 개념] 리액트로 생각하기

리액트의 가장 큰 매력 중 하나는 앱을 설계하는 방식이다. 이번에는 리액트로 상품들을 검색할 수 있는 데이터 테이블을 만드는 과정을 생각해보자. 목업으로 시작하기 JSON API와 목업을 디자이

ykss.netlify.app

 

 

객체지향적인 컴포넌트

그러나 개발자들은 동시에 객체지향적이고 유지보수하기 쉬운 컴포넌트를 작성해야하며 다음의 3가지도 고려해야 합니다.

 

응집도

하나의 목적을 가진 코드는 한번에 작성합니다.

커스텀훅을 기본으로 하되, 핵심 데이터와 세부구현을 구별합니다.

React는 선언형 프로그래밍이 주를 이루며 세부사항은 명령형 프로그래밍으로 작성합니다.

 

단일책임

함수가 여러가지 일을 하지 않고 하나의 일만 합니다. 함수 이름에는 중요 포인트가 모두 포함되어야 합니다.

ex) handlePopup, handleCheck처럼 함수가 한가지 일만 하게 합니다.

 

추상화

추상화를 어느정도까지 하는지는 각자의 성향에 따라 다릅니다. 그 대신 추상화의 수준을 주변 컴포넌트와 맞추는것이 중요합니다.

 

[참고 자료]

 

 

 

또한, 컴포넌트와 컴포넌트를 연결짓다보면 의존성이 생기기 쉽습니다. 가장 간단한 원칙은 '의존성을 끊어내는 방향으로 컴포넌트를 작성하는것'입니다. 컴포넌트에 어떤 하나의 역할이 필요한지 고민하고, 그리고 다른 컴포넌트와의 의존성을 끊어내는 방향으로 컴포넌트를 작성합니다.

예를 들어서, 리스트에서 여러 아이템들을 구성해야한다면 리스트 컴포넌트 내부에 아이템 컴포넌트를 직접적으로 주입하기보다는 '리스트 컴포넌트는 주입된 객체를 여러개 반복해서 생성함'과 '리스트 컴포넌트에 주입할 객체'라는 수준으로 역할을 구분하고 의존성을 끊어내는 것입니다. 이렇게 하면 리스트 컴포넌트는 주입된 객체를 그저 여러개 그릴뿐이며 다른 객체가 주입되어도 동일한 작업을 할 수 있습니다.

 

[참고 자료]

 

복잡한 컴포넌트 유연하게 설계하기

프론트엔드 개발자의 일을 하다보면 복잡한 요구사항의 컴포넌트를 제작할 일이 있습니다. 복잡한 컴포넌트라는게 무엇인지 생각을 해본다면 서버에서 내려준 데이터를 UI로 표현하는게 복잡

velog.io

 

반응형