[TIL/React] 2024/07/10

원민관·2024년 7월 10일
0

[TIL]

목록 보기
141/159
post-thumbnail

Reference:
1) https://bluemiv.tistory.com/87
2) https://emewjin.github.io/feature-sliced-design/
3) https://dev.to/m_midas/feature-sliced-design-the-best-frontend-architecture-4noj
4) https://dev.to/kingsley/mern-stack-project-structure-best-practices-2adk
5) https://velog.io/@zopall0000/Webpack-Vite-Pacel%EB%B2%88%EB%93%A4%EB%9F%AC%EB%9E%80
6) https://www.youtube.com/watch?v=iX3Nu1FcZKA&t=106s

레퍼런스를 읽고 내 입맛에 맞게 해석해보자잉

✅ Directory 구조에 관한 논의

0. 발단

새로운 프로젝트를 시작하기에 앞서, 어떤 폴더 구조로 개발을 진행해야 할 지 고민이 생겼다. 세상에서 가장 어려운 말이 "정답은 없지만"이다. 이럴 때는 남들이 해본 것 중 차선에 가까운 것(Best Practice)을 찾아, 따라 해보는 전략이 유용할 수 있다. 나에게 더욱 정교하게 맞아떨어지는 전략은 차후에 고민하는 것이 옳겠다. 괜찮은 레퍼런스를 몇 개 찾아서 학습해 보려 한다.

1. 고전적인 방식

고전적인 아키텍처라고는 하지만, 사실 고전적 아키텍처라는 것에도 명확한 표준은 없다. 그러나 많은 글 또는 유튜브 영상 등의 자료를 공부하다 보면 일반적으로 다음과 같은 구조를 따른다.

일단 단점부터 얘기해 보면, 고전적인 아키텍처는 컴포넌트 간의 암묵적인 연결과 모듈의 복잡성 때문에 프로젝트의 유지 보수가 어려워지는 경향이 있다. 가령, 아래의 코드와 같은 문제가 발생할 수 있다.

// src/services/apiService.js
export const fetchData = async (url) => {
  const response = await fetch(url);
  return response.json();
};

// src/services/userService.js
import { fetchData } from './apiService';

export const getUserData = async (userId) => {
  return fetchData(`/api/user/${userId}`);
};

// src/containers/HomePage.js
import React, { useEffect, useState } from 'react';
import { getUserData } from '../services/userService';
import Header from '../components/Header';
import Footer from '../components/Footer';

const HomePage = () => {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      const data = await getUserData(1);
      setUserData(data);
    };

    fetchUser();
  }, []);

  return (
    <div>
      <Header />
      <div>
        {userData ? <div>Welcome, {userData.name}</div> : <div>Loading...</div>}
      </div>
      <Footer />
    </div>
  );
};

export default HomePage;

갑자기 정보처리기사에서 학습한 내용이 떠올랐다. 다름 아닌 '결합도'와 '응집도' 관련 이슈다. 좋은 코드라고 평가받을수록 결합도는 낮고, 응집도는 높다. 결합도는 모듈이나 컴포넌트가 서로 얼마나 강하게 연결되어 있는지를 나타내는 척도이고, 응집도는 모듈이나 컴포넌트 내부의 요소들이 얼마나 밀접하게 관련되어 있는지를 나타낸다.

홈페이지는 'getUserData' 함수와, 간접적이지만 'apiService의 fetchData' 함수에 의존하고 있다. 홈페이지가 fetchData 함수와 암묵적으로 연결되어 있는 것인데, apiService나 userService의 변경이 홈페이지에 영향을 끼치기 아주 좋은 구조이다. 더욱이 apiService는 여러 서비스에서 공통으로 사용될 것이다. 결론적으로 위 코드는 결합도가 상당히 높다고 평가할 수 있다.

동시에 응집도는 굉장히 낮다고 볼 수 있다. 사용자 데이터와 관련된 모든 코드가 여러 파일에 분산되어 있다. 이는 특정 기능과 관련된 로직이 여러 곳이 퍼져 있다고 해석할 수 있고, 달리 표현하면 응집도가 낮은 것이다.

내가 헬창이라고 가정해보자.(그럴 일 없음)

만약 상체, 하체 2분할 방식으로 운동을 주 3회 진행한다고 하면, 상체 운동을 하는 날에는 하체 운동을 할 수 없고, 반대로 하체 운동을 하는 날에는 상체 운동을 할 수 없다.

그런데 전면 사슬, 후면 사슬 패턴으로 운동 방식을 변경하면, 전면 사슬 운동(벤치프레스, 스쿼트)을 하는 날에도, 후면 사슬 운동(바벨 로우, 데드리프트)을 하는 날에도 상체와 하체 모두를 조질 수 있다.

상체 하체 2분할 방식이 고전적인 아키텍처를 의미한다. 벤치프레스라는 모듈을 가져와서 써야 하는데 하체를 하는 날이라 적용할 수가 없다.

반면 사슬이라는 기능에 집중한 운동 방식은 후술하게 될 FSD 방식을 나타낸다. 기능으로 운동을 나누었기 때문에 전면 사슬 운동을 하는 날에도 상체와 하체를 모두 운동할 수 있고, 필요하면 다른 운동으로 교체까지 가능하다.

결함이 많은 비유일 수 있으나 결론적으로 얘기하고 싶은 것은, 프로젝트의 규모가 커질수록 확장성을 고려한 코드를 작성해야 하는데, 고전적인 아키텍처 구성 방식은 확장성이 떨어지는 경향이 있기 때문에 다른 디렉토리 구성 방식도 배워놓아야 한다는 것이다. 못 하는 것과 아는데 안 하는 것은 차이가 크기 때문이다.

2. FSD(Feature-Sliced Design)

my-app/
│
├── src/                     
│   ├── features/            # 기능별 모듈 디렉토리
│   │   ├── auth/            # 인증 기능 (로그인, 회원가입 등)
│   │   │   ├── components/  # 인증 관련 컴포넌트
│   │   │   ├── hooks/       # 인증 관련 커스텀 훅
│   │   │   ├── api/         # 인증 관련 api
│   │   │   ├── pages/       # 인증 관련 페이지
│   │   │   ├── types/       # 인증 관련 타입 (typescript 사용시)
│   │   │   ├── constants/   # 인증 관련 상수 값
│   │   │   └── utils/       # 인증 관련 유틸리티 함수
│   │   │
│   │   ├── blog/            # 블로그 기능
│   │   │   ├── components/  # 블로그 관련 컴포넌트
│   │   │   ├── hooks/       # 블로그 관련 커스텀 훅
│   │   │   ├── api/         # 블로그 관련 api
│   │   │   ├── pages/       # 블로그 관련 페이지
│   │   │   ├── types/       # 블로그 관련 타입 (typescript 사용시)
│   │   │   ├── constants/   # 블로그 관련 상수 값
│   │   │   └── utils/       # 블로그 관련 유틸리티 함수
│   │   │
│   │   └── [other features] # 다른 기능들
│   │
│   ├── app/                 # Next.js app 라우팅
│   │   └── page.tsx         # features 내부의 pages와 연결
│   │   └── layout.tsx       
│   │
│   ├── components/          # 전역에서 사용되는 재사용 가능한 컴포넌트
│   ├── hooks/               # 전역에서 사용되는 커스텀 훅
│   ├── api/                 # 전역에서 사용되는 api
│   ├── utils/               # 전역 유틸리티 함수
│   ├── types/               # 전역에서 사용되는 타입 (typescript 사용시)
│   ├── constants/           # 전역에서 사용되는 상수 값
│   ├── store/               # 전역 상태 관리 (예: Context API, Redux, recoil, zustand 등)
│   └── styles/              # 전역 스타일
│
...생략

<components, hooks, api, utils, types, constants, store, styles>(후술에서는 보따리라고 표현하겠음)와 같은 구분을, '기능'이라는 기준의 그릇에 담자는 것이 FSD의 핵심이다.

바깥 보따리는 정말 'common'이라는 목적을 담고 있는 내용만 코드로 구현하자는 것이다. 반면 auth나 blog 내부에 있는 보따리는 그 기능에 맞게 보따리의 내용을 구체화하자는 것을 목표로 한다.

기존의 고전적인 아키텍처를 따른다면 hook은 src 바로 하단에 위치하게 될 것이다. hook을 이곳저곳에서 사용하다 보면 결합도는 높아지고 응집도는 낮아지게 된다.

FSD 방식을 따른다면, auth에서는 auth에 관한 hook을 별도로 정의했기 때문에 타 기능과의 결합도는 낮아지고 auth라는 기능에 대한 응집도는 높아질 수밖에 없다.

3. backend

서당개 3년이면 풍월을 읊는다고,,, 풍월까진 모르겠고 frontend 폴더 구조는 위의 내용처럼 더 좋은 내용을 찾을 줄 안다. 왜냐면 기본 구조가 무엇인지 알고 있었으니까.

그런데 MERN 스택은 처음인지라 폴더 구조를 어떻게 잡아야 하는지 감도 잘 오지 않았다. 오직 직관으로 괜찮아 보이는 레퍼런스를 찾았다. 다음은 Kingsley Amankwah 형님이 작성하신 글의 일부다.

Model, View, Controller,,, 아직 정처기 시험이 2주 정도 남았지만 공부하길 참 잘했다. 꼭 뭘 배워서가 아니라 사실 벼락치기라 아는 것도 별로 없음 특정한 키워드를 만났을 때 거부감이 없다는 점이 정처기의 최대 효용이 아닐까.

config에서는 백엔드 설정 파일에 관한 코드를 작성한다. controller에서는 말 그대로 애플리케이션의 컨트롤러들을 포함한다. models 디렉토리에서는 애플리케이션의 모델이 포함되고 routes에는 라우트가 포함된다. 그리고 server.js는 백엔드 애플리케이션의 진입점으로 사용한다.

더 좋고 말고를 떠나서, 일단 아는 게 없기에 킹슬리 형님의 말씀에 의존해 보기로 한다.

4. 결론

오늘 공부의 본 목적은, 프로젝트 폴더 구조를 결정하는 것이었다. 프론트는 FSD를, 백엔드는 킹슬리 형님의 아키텍처를 채택하기로 결정했다. 향후에 진행하게 될 프로젝트의 규모는 계속 커질 것이고, 확장성을 고려하는 코드를 작성해 보는 습작도 있어야 하기 때문이다. 미래를 위해(=확장성을 고려) FSD(=확장성을 고려)를 선택하겠다. 그런데 문제는 어떤 폴더가 필요할지 고민하는 시간도 상당히 필요할 것으로 예상된드아ㅏ.

✅ Vite 사용 이유(번들러 관련 정리 + SWC)

0. 발단(번들러란?)

프론트엔드 춘추전국시대를 활개치고 다니는 원민관 장군. 번들러 이슈도 피할 수 없었다.

번들러란 이거다.

재미가 없다, 재미가.

이게 번들러다. 사실 지성인이라면 스텔라를 마시는 것이 일반적이다. ro소리임

번들러는 여러 개의 자바스크립트 파일과 모듈을 하나의 파일 또는 소수의 파일로 묶는 도구를 의미한다. 코드 베이스가 큰 폭으로 증가하다 보면 애플리케이션의 속도가 느려져 답답함을 느낀다. 번들러는 코드를 압축하고 최적화하여 파일 크기를 줄이고 성능을 향상하는 등, 위에서 느낀 문제점을 해결해 주는 좋은 툴이다.

대표적인 번들러로 Webpack, Parcel, Rollup, CRA 등이 있다. 사실 오늘의 주인공은 Vite다.

1. Vite란?(사용 이유)

아기 개발자라 용어들이 너무 어렵다. Webpack과 CRA를 선택하지 않는 이유, 동시에 Vite를 선택해야 하는 이유에 대해 간단하게 서술하는 것을 오늘의 목표로 삼겠다.

Webpack을 선택하지 않는 이유
1. 개발 서버를 시작할 때, 모든 모듈을 합치기 때문에 느리다.
2. 간단한 작업도 플러그인이 필요하다.
3. 번들 사이즈가 너무 크다.

CRA를 선택하지 않는 이유
1. 사용하지 않는 기능까지 전부 설치되기 때문에 모듈의 사이즈가 크다.
2. 커스텀 빌드를 하는 것이 어렵다.

번들러계의 GOAT, Vite를 사용하는 이유는 단순하다. '빨라서'.

Go라는 언어로 만든 엄청 빠른 번들러 라이브러리가 바로 esbuild인데, Vite는 이러한 esbuild를 사용하고, 라이브러리를 설치하자마자 미리 bundle을 만들어 놓는다고 한다. 소스코드는 필요한 것만 건드린다. HMR(Hot Module Replacement)이라고 하는데, 수정된 파일만 교체하는 방식을 의미한다.

webpack을 사용한 리액트는 초기 렌더링에 6초가 소요되는데, vite를 사용한 리액트는 초기 렌더링에 0.37초가 소요된다. 음지의 생활코딩, '코딩애플'님의 말씀이라 틀림없이 진실이다.

+) SWC(Speedy Web Compiler)

추가적으로, 초기 vite 설정을 할 때, JS 또는 TS에 SWC를 추가할 것이냐는 프롬프트가 나온다. 어쨌든 컴파일러니까 추가하는 게 좋지 않을까. 이 부분은 솔직히 잘 모르겠다.

profile
Write a little every day, without hope, without despair ✍️

0개의 댓글