과제의 핵심취지
- React의 hook 이해하기
- 함수형 프로그래밍에 대한 이해
- 액션과 순수함수의 분리
과제에서 꼭 알아가길 바라는 점
- 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
- 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
- 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
- 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)
배포 링크
https://legitgoons.github.io/front_6th_chapter2-2/
기본과제
-
Component에서 비즈니스 로직을 분리하기
-
비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기
-
뷰데이터와 엔티티데이터의 분리에 대한 이해
-
entities -> features -> UI 계층에 대한 이해
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
특정 Entitiy만 다루는 함수는 분리되어 있나요?
-
특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?
-
데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?
심화과제
-
이번 심화과제는 Context나 Jotai를 사용해서 Props drilling을 없애는 것입니다.
-
어떤 props는 남겨야 하는지, 어떤 props는 제거해야 하는지에 대한 기준을 세워보세요.
-
Context나 Jotai를 사용하여 상태를 관리하는 방법을 익히고, 이를 통해 컴포넌트 간의 데이터 전달을 효율적으로 처리할 수 있습니다.
-
Context나 Jotai를 사용해서 전역상태관리를 구축했나요?
-
전역상태관리를 통해 domain custom hook을 적절하게 리팩토링 했나요?
-
도메인 컴포넌트에 도메인 props는 남기고 props drilling을 유발하는 불필요한 props는 잘 제거했나요?
-
전체적으로 분리와 재조립이 더 수월해진 결합도가 낮아진 코드가 되었나요?
과제 셀프회고
전체 아키텍처(Basic)
학습 목표
일단 테오가 알려준 학습목표를 생각하면서 과제를 진행하려고 노력했습니다.
1. React에서 추상화 벽이 만들어진 코드의 계층과 경계를 이해하기. 코드를 어떻게 잘 분리를 해두는게 좋은가?
- 이번 과제에서는 크게 4단계로 나누어서 생각했습니다.
- 데이터 / 모델 (비즈니스 로직) / 로컬 상태(데이터를 비즈니스 로직 태운 다음 local에서 사용하기 위한 상태) / 컴포넌트
- 결합도가 낮고, 응집도가 높으면 → 기능 통째로 날려도 잘 돌아가야 한다
- cart가 어떻게 돌아가는지, product가 어떻게 돌아가는지 서로 알 필요가 없다
- 이 지점에서 FSD가 최근 각광받는 이유도, 기능별 / 레이어별로 분할이 잘 되어 있어 서로 서로 어떻게 돌아가는지 모르고, 낮은 결합도와 높은 응집도를 제공할 수 있기 때문이라고 생각합니다.
- 물론 이번 과제에서 쓰기에는 오버엔지니어링이라고 생각하기도 했고, 단순한 구조에서 과제를 진행하며
아키텍처 개선이 이래서 필요하구나를 느껴보고 싶어서 hint의 구조를 참조해서 진행했습니다. - 코드가 충분히 분리되어 많은 파일이 생긴 basic 과제 막바지 쯤, 같은 도메인의 코드끼리 묶여져 있지 않은 것의 불편함을 느꼈습니다. 그러면서 UIToast를 FSD처럼 폴더 내에서 세그먼트를 분리하고, 캡슐화를 해서 UI만 export해보았습니다. 해당 커밋
- 물론 이번 과제에서 쓰기에는 오버엔지니어링이라고 생각하기도 했고, 단순한 구조에서 과제를 진행하며
2. 계층의 분리의 과정에서 순수함수의 개념과 디자인 패턴이 어떤 도움을 주는가?
- 기본적으로는 재사용성을 높이고, 테스트를 쉽게 하고, 이름만으로 내부를 추측하게 해 가독성에 도움을 줍니다.
- 하지만 모두 적절한 추상화가 있어야 가능합니다.
const filteredProducts = debouncedSearchTerm
? products.filter(
(product) =>
product.name.toLowerCase().includes(debouncedSearchTerm.toLowerCase()) ||
(product.description && product.description.toLowerCase().includes(debouncedSearchTerm.toLowerCase()))
)
: products;
- 과제를 진행하면서 위의
filteredProducts함수를 어떻게 분리해야할지 몰라 일단 모두 나눠보았었습니다.
normalizeSearchTerm() // 1. 검색어 정규화
isValidSearchTerm() // 2. 검색어 유효성 체크
matchesProductName() // 3. 상품명 include 여부 확인
matchesProductDescription() // 4. 상품설명 include 여부 확인
matchesProduct() // 5. 상품 필터링 로직
filterProductsBySearchTerm() // 6. 메인 로직
- 하지만 지나친 분리로 오히려 읽기 힘들어지는 과도한 추상화라는 생각이 들어 다시 이를 아래와 같이 수정했습니다.
normalizeSearchTerm() // 1. 검색어 정규화
matchesProduct() // 2. 검색어 유효성 체크 ~ 4. 상품설명 include 여부 확인
filterProductsBySearchTerm() // 5. 상품 필터링 로직 ~ 6. 메인 로직
- 검색어 정규화 처리는 재사용의 여지를 고려하여 단독으로 분리했습니다.
- 유효성 체크 ~ 상품설명 include 여부 확인에서는 메인 비즈니스 로직을 과하지 않은 선에서 묶어주고자 했습니다.
3. 컴포넌트에 대한 계층구조와 상태관리. 컴포넌트 계층 분리는 어떻게 해야 하나?
- UI 컴포넌트와 Entity 컴포넌트로 나눠서 생각해봤습니다.
- 원래는 이렇게 생각했습니다(as-is)
- Entity 컴포넌트: 상태를 import해서 사용하는 컴포넌트
- UI 컴포넌트: 데이터를 사용하지 않는 컴포넌트
NOTE
- 지훈님의 피드백: "장바구니 담기 버튼도 sold out 여부에 따라 바뀌는데?" → UI 컴포넌트는 데이터를 몰라도 props로 받아서 처리할 수 있어야 한다!
- 바뀐 생각(to-be)
- Entity 컴포넌트: 데이터를 직접 가져와서 사용하는 컴포넌트
- UI 컴포넌트: data를 주입받아서 사용하는 멍청한 컴포넌트
- 예를 들자면 다음과 같습니다.
ItemCard (데이터 컴포넌트)
├── 상품 데이터를 직접 가져옴
├── sold out 여부 판단
└── AddToCartButton (UI 컴포넌트)
├── 데이터를 주입받아 처리
└── 멍청해서 시키는 대로만 동작
- 분리해야 하는 이유
- 재사용성: UI 컴포넌트는 멍청해서 재사용이 쉽다
- 가독성 & 유지보수성: 데이터와 엮인 컴포넌트를 데이터 가까이 둘 수 있다
- 만약 재사용하지 않는 UI 컴포넌트도 분리해야할까?
- 가독성 문제: 데이터 컴포넌트를 데이터 옆에 두고 싶다
- 유지보수: 구조 파악이 어려워지고 불필요한 컨텍스트를 읽어야 함
4. 컴포넌트 계층 분리를 하는 과정에서 만나게 되는 props drilling problem을 이해하고 개선 방안 찾기
- 원래 저는 state를 최상단의 각 페이지들에서 내려주는 방식으로 구현하는 걸 선호했습니다. 그리고 그 방법이
props를 통해서 의존성을 보여주는 방식이라고 생각하고 있었습니다. - 이번 과제를 통해서 적절한 props drilling의 기준에 대해서 생각해볼 수 있는 기회가 되었습니다.
notification과productForm,couponForm만 Jotai를 사용해 수정하였습니다.notification: 비즈니스 로직과 무관하게 모든 컴포넌트에서 사용할 수 있는 횡단 관심사 컴포넌트이기 때문- 고로 의존성을 보여줄 수도 없고, 어디서든 재사용이 가능해야 함 -> 전역으로 사용하는게 확실히 이득
productForm&couponForm: 의존성을 보여줄 수 있기에 굳이 전역으로 관리할 필요는 없음- 하지만 지나치게 많은 인터페이스가 노출되고 있어 단순화의 필요성을 느낌
- 사실 프로젝트였다면 현 시점에서는 추가적인 depth가 없으니 수정을 하지 않았을 수도 있겠지만, props drilling을 체험하는 과제인 만큼, 조금 적극적으로 jotai를 사용했습니다.
- 저에게 기준을 정하라고 한다면, 횡단 관심사이거나 앱의 크기에 비해서 depth가 깊고 인터페이스가 많을 때 전역 상태관리를 고려해볼 것 같습니다.
과제를 하면서 내가 알게된 점, 좋았던 점은 무엇인가요?
알게된 점
- as-is: 1년 반 전에 함수형 액션/계산 분리 찍먹해봄, 아키텍처 모름(MVC 패턴도 이해못함)
- to-be: 액션/계산/데이터 분리가 왜 중요한지 이해함, 레이어 분리, 기능 분리의 중요성과 아키텍처가 어떻게 구현되는 것인지 깨달음
좋았던 점
- 다른 사람들과 적극적으로 이야기를 나누면서 진행한 것이 가장 좋았습니다. 이번 주 과제를 하면서 특히 많은 이야기를 나눴던 병준님과 지훈님, 휘린님께 감사드립니다.
- 병준님: 레이어드 아키텍처와 Store/Service 계층 분리 방식 공유
- 지훈님: 산파법을 통한 컴포넌트 계층 학습, 기준 세우기의 중요성
- 휘린님: Hook과 UI 분리, FSD 아키텍처에 대한 친절한 설명
이번 과제에서 내가 제일 신경 쓴 부분은 무엇인가요?
데이터 → 모델 → 로컬 상태 → 컴포넌트로 레이어를 분리하는 것
- hint의 단계대로 레이어를 분리하면서, 각 레이어가 다른 단계의 레이어를 침범하지 않도록 했습니다.
AI 적당히 쓰기
- 최근 함께 자라기라는 책을 읽고 있는데, 난이도가 실력보다 너무 높으면 불안함을 느끼고, 반대로 난이도가 지나치게 낮으면 지루함을 느낀다고 하더라구요.
- 그리고 불안함을 느끼고 있다면 해결방안으로 제시된 것이 AI등을 사용해서 난이도를 낮추는 것이였습니다.
- 이에 지난 주차 과제에서는 적극적으로 AI를 사용했고, 불안함에서 벗어날 수 있었습니다. 하지만 반대로 AI를 지나치게 사용하다보니 남는게 없다는 생각이 들었습니다.
- 이번 주차에서는 AI는 검색을 대신하는 용도와 자동 완성, 그리고 단순 작업을 위주로 사용했습니다.
- 이렇게 사용하는 동안 느낀 점은, 일단 지난주에 적극적으로 사용했던 agent가 생각보다 굉장히 멍청하다는 것
- 그리고 직접 hook과 model 부분을 작성하면서 프론트엔드의 추상화 벽이 나눠지는 것을 이해할 수 있었습니다.
- 앞으로도 처음해보는 것을 곧바로 AI로 작업하기 보다는, 일단 직접 해보면서 이해를 하고 난 후 AI로 작업하려고 합니다.
이번 과제를 통해 앞으로 해보고 싶은게 있다면 알려주세요!
- 이번 과제를 진행하면서 프론트엔드 아키텍처에 대한 시야를 넓힐 수 있었습니다. 특히 병준님의 경우 레이어드 아키텍처를 사용해서 상태 변경은 Store에, 비즈니스 로직은 Service 계층에 분리하는 방식을 진행하셨는데, 저도 이런 방식으로 진행해보고 싶습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
- 함수 분리 기준: 코치님은 어떤 기준으로 함수를 분리하시나요?
- Jotai 적용 범위: productForm과 couponForm에도 Jotai를 적용했는데, 객체로 묶어서 props로 전달하는 것이 더 나았을까요?
- 액션 함수 처리 방식: 클로저 vs 명시적 매개변수 전달 중 어떤 방법을 선호하시나요?
과제 피드백
안녕하세요 의찬님!
회고가 전체적으로 인상적이네요 ㅎㅎ
로직을 분할했다가 다시 합치는 과정도, 다른 사람들의 코드를 참고하는 과정도, AI를 통해 난이도를 조절하는 과정도. 의찬님의 모든 고민이 잘 느껴져서 좋았어요
함수 분리 기준: 코치님은 어떤 기준으로 함수를 분리하시나요?
저는 재사용을 하기 위함도 있지만, 추상화 수준을 정렬하기 위해 분리하는 경우가 많은 것 같아요 ㅎㅎ
가령
array.find();
filterXXX();
이런 형태로 되어있을 때, array.find()는 날것의 코드로 표현이 되어있고, filterXXX()는 한 단계 더 추상화가 되어있습니다. 이럴 때 아예 findXXX, filterXXX 처럼 추상화를 정렬시키기 위해 분리시키는거죠 ㅎㅎ
그래서 이런 글도 작성했답니다!
Jotai 적용 범위: productForm과 couponForm에도 Jotai를 적용했는데, 객체로 묶어서 props로 전달하는 것이 더 나았을까요?
이왕 사용하는김에 적극적으로 사용하는 모습이 좋다고 생각해요 ㅎㅎ 다만, 공용화해서 사용해야 한다는 전제가 있다면 props로 넘겨주는게 좋을 것 같습니다.
액션 함수 처리 방식: 클로저 vs 명시적 매개변수 전달 중 어떤 방법을 선호하시나요?
흠... 이건 어떤 의미인지 이해하기가 어렵네요 ㅎㅎ 구체적인 코드가 필요할 것 같아요. 이건 따로 디스코드에 문의 남겨주시면 답변드리도록 하겠습니다!