최근에 다른 사람들과 같이 코드를 짜볼 기회가 늘어나면서 신기한 경험을 했다.
// React Component
export default function App({}: AppProps){}
const App:React.FC<TProps> = ({}) => {}
const App:FC<IProps> =({}) => {}
const App = ({}:Props) => {} // 등등
놀랍게도 ..같은 컴포넌트를 작성할때 나왔던 코드들이다.
리액트를 사용할때 가장 기본적인 컴포넌트 정의 스타일부터 여러개로 갈렸다. props 를 컴포넌트 내부에서 구조분해할당하여 사용하는 분도 계셨고, props type을 type 으로 정의하냐, interface 로 정의하냐라는 주제로 가벼운 토론도 해봤다.
이렇게 같은 언어, 같은 라이브러리(프레임워크) 를 사용해도 나올 수 있는 코드 스타일은 너무나 다양했다. 모두와 코드 스타일에 대한 약속을 정하지 않으면 본인의 방식대로, 편한대로 하나의 프로젝트에 여러 스타일이 같이 존재하게 된다. 함께 작업하는 사람이 많아질 수록 이는 더 심해질 것이다. 이런 상황에서는 분명 읽기도 어렵고, 관리하기도 힘든 프로젝트가 될 것이다.
이를 방지하기 위해 회사에서는 코드를 작성할 때 지켜야할 룰을 만들고 사용 중이다. 언젠가 회사의 개발팀이 프론트팀으로 분리하면서 새로운 코드 컨벤션을 만들어보자고 제안, 회의를 해본적이 있었다. 이때 나는 기존에 존재하던 유명한 자료들을 직접 찾아보고 문서를 작성해보기도 하였다.
프로젝트를 진행하면서 일관적인 코드 스타일 , 구조화된 폴더 구조로 인한 장점들을 몸소 느꼈고 혼자 프로젝트를 할 때도 일관적인 규칙을 적용하며 코드를 작성하면 괜찮을 것 같은 생각이 들었다. 회사에서 문서를 작성하던 경험을 살려 내가 사용해볼 코딩 컨벤션 문서, 그것을 이용한 프로젝트 초기 세팅 템플릿 레포지토리를 만들었다.
컨벤션 문서
bit.ly/code_convention_ghoon99
초기세팅 템플릿 레포지토리
https://github.com/GHooN99/my-ts-react-template
위의 결과물들을 만들면서 생각한 내용들을 기록해본다.
컨벤션
Git 관련
- 커밋 메시지
좋은 커밋 메시지의 중요성, 그리고 다양한 방법론들이 존재했다. 나도 그것을 최대한 따라가려고 노력했다.
커밋 메시지를 Gitmoji 라는 것을 이용하여 작성하는 사람들도 있다.
적응되면 편하다고는 하는데.. 처음에 어떤 것을 써야 할지 고르기도 힘들고 남이 쓴 커밋 메시지도 알아보기 힘들었다.
결국 낯섦의 문제일지도 모르겠다만, 이런 것까지 익히고 쓰는것이 효율적인가? 라는 생각이 들기도 한다.
나는 GitKraken 이라는 GUI 툴로 다른 사람들의 커밋메시지, 커밋 로그들을 살펴보는 편인데 ,이모지 대신 글자로 되어있는 것이 훨씬 직관적이고 읽기 편했다.
같은 맥락으로 우리는 근본적으로 "한국인"... 이기 때문에 어쩔수없이 한글이 익숙할 것이다. 무의식적으로도..
나는 영어로 메시지를 작성하는 것 보단 한글이 편하고 알아보기 쉬웠다. 그래서 커밋 메시지는 한글로 작성 한다라는 룰을 만들었다.
- 브랜치 전략
이 역시도 이미 좋은 브랜치 전략들이 많이 알려져있다. 그것과 비슷한 형식을 따왔다.
main 브랜치에서 분기를 하냐, develop 브랜치에서 분기를 하냐 에 따라 다른 전략을 가져갈 수 도 있을 것 같다.
나는 두가지 방식 모두를 사용해보았다. main 은 상용서비스 서버, develop 은 테스트서버를 바라보는 브랜치라는 것은 동일했다.
Sqush Merge vs Merge Commit or Rebase...
코드를 특정 브랜치에 합치기 전 깃허브에 PR 을 올린다. PR이 승인 된 후 , 어떻게 브랜치를 합치냐에 따라 커밋 로그가 달라지게 된다.
개인적으로는 feature -> develop 은 Sqush Merge 가 괜찮은 것 같다. feature 브랜치에 자잘한 변경 사항까지 develop 에 존재할 필요는 없어보인다.
특이하게 rebase 는 git 명령어에서 없는 취급을 해야..한다고 주변에서 들었다. 이상하게 rebase 를 할때마다 한번씩 뭔가가 꼬인다고...
그리고 실제로 옆에서 rebase 로 고생하는 팀원을 몇번 봤다..
위의 내용들은 어떤 형태의 프로젝트인지에 따라, 많이 달라질 수 있을 것이다.
각자의 장단점을 이해하고 적절한 방식을 유동적으로 적용하되, 한번 적용했으면 구성원들끼리 꼭 지키는 것이 중요한 것 같다.
공통 폴더 구조
- 폴더구조의 고민
제일 많은 시간동안 고민한 내용이며 끝까지 풀리지 않을 문제일 것이다.
리액트..라는 라이브러리는 너무나도 자유도가 높다. 깃허브에 올라와있는 프로젝트들의 폴더 구조는 너무나 다양했다.
폴더구조를 정하는 동안은 이 폴더 구조를 어떻게 보기좋게 잘 관리할까, 어떻게 하면 수 많은 파일들의 탐색비용을 줄이고, 연관된 코드들의 결합도를 높일 수 있을까 라는 질문을 던질 수 있었던 시간이였다.
- 공통부분
import 경로를 tsconfig 설정을 이용하여 단축 시킬 수 있다. import usehook from "../../../hooks/usehook" 같은 걸 막을 수 있었다.
src 의 바로 밑 폴더 이름 앞에 root 라는 접두사를 붙였다. 이는 조금 더 "공통의" (혹은 "root 레벨") 이란 느낌을 폴더명에서부터 느낄 수 있도록 의도적으로 붙였다.
실제로 root.component 는 공통 UI 컴포넌트가 위치한 폴더이며, root.hooks, root.lib/utils는 각각 프로젝트공통으로 사용할 수 있는 커스텀 훅, 유틸 함수들을 모아둔 폴더이다.
root.feature 폴더
가장 고민을 많이 했던 부분이다.
지금은 하나의 기능 (feature) 을 기준으로 폴더를 분리했고 , 이 기능에는 화면의 시작점들이 존재한다. (screen)
리액트 컴포넌트는 UI 를 담당하는 (Jsx Element 를 반환하는 ) 함수 라고 생각하며, 이 내부에 최대한 UI 와 동적인 상태들을 핸들링 하는 코드들만 남겨두려 했다.
분명 재사용 되지는 않을 것 같지만
- 굳이 컴포넌트 내부에서 필요없는 코드를 분리하기 위해 (외부에서 import 해와서 호출만)
- 가독성을 위해
분리한 함수나 커스텀 훅들이 존재하게 될 것이다.
그렇다면 이 기능, 이 화면 , 이 도메인에서만 공유되는, 혹은 1번밖에 쓰이지 않는
유틸 함수 , 커스텀 훅 , 타입들은 src 밑의 공통 폴더들 (보통 src/hooks, src/utils) 에 들어가는 것이 맞을까??
이런 고민을 해결하기 위해 특정 페이지에서만 사용되는 컴포넌트들 , 커스텀 훅들, 그리고 유틸 함수 ,상수 등등을 한 곳에 묶어 관리를 해보려고 시도했다.
그리고 역시 다른 사람들도 비슷한 시도를 많이 하고 있었다.
출처 : 리액트 React 로 리팩토링! 웹 프론트엔드 개발자 모여라 | RIDIBOOKS web 1부 | 리디 RIDI dev
이런것도 찾아보고 참고해보았다. (너무 좋은 글 같아서 공유해본다)
https://ahnheejong.name/articles/package-structure-with-the-principal-of-locality-in-mind/
결국 이런 폴더 구조가 완성되었다. root 레벨의 폴더들을 그대로 옮겨놓은 느낌이다. 하지만 이 도메인, 기능, 화면에서만 사용될 것이다.
내가 작성한 폴더 구조는 회사에서도 비슷한 느낌으로 사용 중이다.
특정 화면(기능)에 대한 수정사항이 생기게 된다면 , 딱 해당하는 feature 폴더에만 집중하여 수정할 수 있었다. 그 주변에 필요한 컴포넌트, 커스텀 훅, 비지니스 로직들이 존재하니 폴더 탐색 비용이 줄어든 장점이 있었다. 하지만 기능이 커지고 복잡할수록 동시에 feature 폴더 내부도 복잡해진다는 점 , 폴더 깊이가 너무 깊어진다는 단점이 있었다.
지금은 feature 라고 이름을 지었지만 , 화면을 기준으로 (page, screen) 폴더를 분리 할 수도 있다는 생각이 들었다. 프로젝트를 진행하면서 page 단위, screen 단위로 자연스럽게 기능을 나누고 있었던 것을 발견하였기 때문이다.
다양한 프로젝트를 진행하면서 많은 사례들이 생기길 것이며, 그것을 통해 앞으로 연구를 더 많이 해볼 예정이다.
root.libs ??
lib 라는 이름이 아직도 모호하다. 의도는 types , utils , constant 같은 유틸형 파일들을 모아둔 곳 이라는 느낌을 주고 싶었다. 물론 이들도 root 레벨의 폴더로 만들 수 있을 것 같은데(src/constant, src/utils , 실제로 그런 프로젝트도 상당히 많았다)
이미 root 레벨의 폴더가 많이 존재하므로 비슷한 느낌을 주는 폴더끼리 libs 라고 묶었다.
root.remotes
서버 상태 관리 (server state), 서버 통신을 위한 코드를 모아둔 폴더이다. 서버 상태와 클라이언트 상태에 대한 내용을 접하게 되고, 어떤 방식으로 서버통신과 관련된 코드를 관리할까에 대해 생각해본 결과물이다. 이 폴더구조를 만들 때 제일 나중에 만들어진 폴더이다.
https://tkdodo.eu/blog/practical-react-query#client-state-vs-server-state
REST API 통신에 대한 행동을 추상화하여 인터페이스를 작성해보았다. 나중에 같은 인터페이스를 구현하는 mock API 를 만들 수도 있을 것이고, axios 가 아닌 다른 http 통신 모듈로 구현을 할 수 있을 것이다. 나름대로의 추상화를 시도해본 결과물.
현재는 axios 을 이용하여 API 인터페이스를 구현을 하였으며 이 구현체에는 axios intercepter 등을 처리하는 코드도 존재한다.하나의 API 인스턴스를 생성하여 프로젝트 전체에서 사용한다. baseURL 이 여러개인 경우 어떻게 다룰지도 고민을 해봐야 하겠다.
아직 REST API 형식의 프로젝트를 많이 진행해본 적이 없어서 더 경험이 필요한 부분이다 (회사에선 gql 을 사용 중)
root.remote 안의 폴더는 특정 도메인, 기능의 이름을 가진 폴더가 존재한다.
이름이 feature 인 폴더 그 밑엔 feature.services.ts , feature.types.ts , feature.keys.ts 가 존재한다.
- feature.service.ts
도메인, 기능 별로 API 호출 endpoint 가 존재할 것이고 , 그것을 service 객체로 묶어 관리를 한다. 그리고 요청의 행동에 대한 이름을 붙여 어떤 기능의 API 인지 알기 쉽게 한다.
API 를 직접 호출하는 것이 아닌 해당 서비스 객체 내부의 메소드를 실행시키는 것은 API 호출 내부의 생김새에 관심을 분리 시키는 역할을 할 수 있는 것 같다. 같은 API 를 사용하는데 호출 주소가 달라진 경우 객체 내부의 주소만 수정하기만 하면 된다는 장점을 가지고 있다.
- feature.keys.ts
비동기 상태관리 도구로서 리액트 쿼리를 이용한다. 리액트 쿼리의 쿼리 키라는 개념은 아주 중요한 역할을 해서 잘 관리해야 한다.
https://tkdodo.eu/blog/effective-react-query-keys
이글을 참고하여 쿼리키를 관리하는 파일을 로직별로 설정하였다. 너무 좋은 글이므로 꼭 읽어보길 바란다.
리액트 쿼리를 커스텀 훅으로 한번 더 묶어 사용한다. 이미 API 호출 함수에 제네릭으로 타입이 지정되어있기 때문에 useQuery 의 결과물 data 의 타입 지원이 가능해진다.
// todo detail 정보를 가져오는 커스텀 훅
export const useTodoDetailQuery = (todoId: number) => {
// 필요하다면, 전처리
return useQuery(
todoKeys.detail(todoId), // query key
todoService.getTodoDetail(todoId), // fetch function
{} // 필요하다면 option
);
};
// 사용하는 쪽
const {isLoading , data} = useTodoDetailQuery(id);
// data:Todo
- feature.types.ts
외부 데이터의 응답값들의 타입은 이곳에서 같이 관리한다.
이로서 외부(서버)에서 불러오는 데이터의 타입들 ,함수, 키들이 폴더 채로 잘 묶여 클라이언트 내부에서만 사용하는 상태, 데이터들과 잘 분리가 된 느낌을 준다.
root.styles
전역적인 스타일 , 테마 , css 애니메이션 등 공통적으로 쓰일 수 있는 스타일 관련 코드들이 존재하는 곳이다. 색상 코드를 정의하고, 그것을 의미적으로 묶은 색상관련 객체들을 만들고 그것을 또 묶어 한개의 테마 객체로 만든다.
styled-component 를 사용중이라 theme type 지원을 위한 styled.d.ts 파일도 만들었다.
코딩 스타일 가이드 부터는 다음글에..
이외 나머지 폴더들은 문서를 한번 구경해보길 바란다.
다음 글은 코딩 스타일 가이드, 그리고 이것들을 바탕으로 만든 템플릿 레포지토리, 느낀점들에 대한 내용이다.
끝
'기술 메모장' 카테고리의 다른 글
오픈소스 저장소, PR에서 볼 수 있는 것들 (feat. react-query, @toss/slash) (2) | 2022.11.16 |
---|---|
나의 코딩 컨벤션과 폴더구조를 소개합니다. #2 (스타일 가이드와 템플릿 레포지토리) (2) | 2022.10.18 |
React Router를 직접 만들어보는 과정 (with 프리온보딩 FE 챌린지 10월) (1) | 2022.10.09 |
UNITHON 9TH 참가 후기 - 찐 I의 2박3일 오프라인 해커톤 도전기 (1) | 2022.09.13 |
원티드 프리온보딩 챌린지 8월 참여 후기 (4) | 2022.09.11 |