배포 url https://tooth-is-silver.github.io/front_6th_chapter2-2/
과제의 핵심취지
- React의 hook 이해하기
- 함수형 프로그래밍에 대한 이해
- 액션과 순수함수의 분리
과제에서 꼭 알아가길 바라는 점
- 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
- 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
- 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
- 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)
기본과제
-
Component에서 비즈니스 로직을 분리하기
-
비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기
-
뷰데이터와 엔티티데이터의 분리에 대한 이해
-
entities -> features -> UI 계층에 대한 이해
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?
-
주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?
-
계산함수는 순수함수로 작성이 되었나요?
-
특정 Entitiy만 다루는 함수는 분리되어 있나요?
-
특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?
-
데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?
심화과제
-
재사용 가능한 Custom UI 컴포넌트를 만들어 보기
-
재사용 가능한 Custom 라이브러리 Hook을 만들어 보기
-
재사용 가능한 Custom 유틸 함수를 만들어 보기
-
그래서 엔티티와는 어떤 다른 계층적 특징을 가지는지 이해하기
-
UI 컴포넌트 계층과 엔티티 컴포넌트의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
-
엔티티 Hook과 라이브러리 훅과의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
-
엔티티 순수함수와 유틸리티 함수의 계층의 성격이 다르다는 것을 이해하고 적용했는가?
과제 셀프회고
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
기본
-
UI 분리 기준에 대한 고민 4주차때처럼 더러운 코드를 아주 잘게 쪼개는 것이 아닌 props drilling을 경험하기 위한 UI 분리를 진행해야해서 어느 정도까지 컴포넌트화해야 할지 기준이 애매했다.
<ProductList />컴포넌트의 경우 ProductList말고 ProductCard로 분리해서 작업한 팀원들도 있었는데 ProductList가 부모 컴포넌트인<CartPage />에서 어떤 부분을 보여주는지에 집중해서 ProductCard로 더 나누지 않고 명확한 컴포넌트를 가져가고자 했다.{/* 상품 목록 */} <section> <div className="mb-6 flex justify-between items-center"> <h2 className="text-2xl font-semibold text-gray-800"> 전체 상품 </h2> <div className="text-sm text-gray-600"> 총 {products.length}개 상품 </div> </div> {filteredProducts.length === 0 ? ( <div className="text-center py-12"> <p className="text-gray-500"> "{debouncedSearchTerm}"에 대한 검색 결과가 없습니다. </p> </div> ) : ( <ProductList products={filteredProducts} addToCart={addToCart} getRemainingStock={getRemainingStock} /> )} </section> -
공용 컴포넌트화 중복 코드를 제거하기 위해 label text가 있는
<Input />기본 공용 컴포넌트를 생성헀다. 공용으로 모두 사용할 수 있을거라 생각했지만 label text가 없는 형태와 Select 박스도 Input 형태와 유사한 것을 확인했다. 여유가 있었다면 예외케이스의 UI도 모두 추가하였을테지만<Input />만 적용하였고 시도해본 것에 의의룰 두었다.<Input labelText="상품명" type="text" value={productForm.name} onChange={(e) => setProductForm({ ...productForm, name: e.target.value, }) } required /> -
AI 디톡스 이전 주차에서 아주 더러운 코드와 친해지는 계기가 있었는데, 이번 주차에서는 자체적으로 AI 디톡스를 시행했다. 생각보다 만족스러웠는데, 이전 주차에서는
어라.. 내가 한 게 거의 없는거 같네... 그치만 깨끗해졌죠?라는 느낌이었다면 이번 주차는어라... 손코딩이 이렇게 재밋는거였나? 그치만 코드가 더럽죠?라는 느낌이었다. Ask모드로 변수명이나 폴더 구조를 물어보는 경우가 대부분이었고 refactoring(hint) 폴더도 도움이 많이 되었다. -
비즈니스 로직 & 핸들러 분리를 어디까지 할 것인가 솔직히 순수 함수로 잘 나눴냐고 하면
아니다라고 대답할 것 같다. 머리로는 이해하고 시도하려고 했으나 순수 함수로 나누려면 로직들을 세세하게 쪼개놔야할 것 같은데, 쪼개 놓을때 props는 어떻게 할 것이며 의존도가 높은 외부 함수 전달 부분도 어떻게 할지 막막했다.Cart,Product,Couponentity별로 나눈다고 나눴는데 handler/엔티티 기준으로 나누려고 했고, 나눠보니 생각보다 많은 로직이 있어 보였다.productHandler내부 로직을 보면setProductForm을 받아서 업데이트 하는 부분이 있는데 두 함수에서 모두 사용중이다. 해당 로직을 분리하는게 맞는걸까?setProductForm함수를 외부에서 전달받고 있으며 단순 연산이나 액션이 아닌 데이터 적재의 부분이어서 스스로의 작업 기준이 애매하다보니 시도해보지는 못했다. 이 부분은 많은 코드를 보고 보는 눈을 길러야 할 것 같다.setProductForm({ name: "", price: 0, stock: 0, description: "", discounts: [], }); -
아무리 그래도 불필요한 props 전달 지양 처음에는 아무 생각없이 App에서 무조건 props를 내려주는 방법으로 작업했는데, 작업하다보니 전달하는 props가 과연 해당 컴포넌트에서 필요한지 체크해보게 되었다. 관련 작업을 했는데, 해당 작업에서도 couponForm이나 showCouponForm같은 상태 값들을 App에서 drilling으로 전달하는게 아니라 필요한 곳에 선언해서 사용하게끔 하니 가독성이 좋아졌다.
심화
-
Jotai와의 첫 만남 단순하게 쓸 수 있어서 너무 좋았다.
리액트스럽다가 이런 느낌인가보다.atom으로 기본 값을 세팅해서 사용할 수도 있고, get, set인자를 활용하여 내부 로직도 추가할 수 있어 함수느낌도 나고 상태 관리의 action 느낌도 나서 애용할 것 같다. -
최대한 엔티티 의존도 줄이기 props drilling을 줄이면서 최대한 엔티티(도메인 데이터)를 jotai로 개선하고자 했다. 자연스럽게 엔티티뿐만 아니라 내부에서 불러다 쓸 수 있는 hooks이나 상태값들도 필요한 컴포넌트에서 jotai로 불러다 쓰면 되니 props가 사라지는 쾌감이 너무 좋았다.
// AS-IS
export const Header = ({
isAdmin,
setIsAdmin,
cart,
searchTerm,
setSearchTerm,
}: HeaderProps) => {
// TO-BE
export const Header = () => { // 모두 제거 편-안
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
-
핸들러나 UI내부에 분리해내지 못한 로직 중 UI에서 onClick, onChange, onBlur로 호출하는 유효성 검사 함수를 분리할 수 있었을거 같은데 방향이 잡히지 않아 진행하지 못했다. 다른 분들의 코드를 보고 작업해볼 수 있었을거같은데 시간이 여유롭지 않아 보류하였다. 코멘트 링크
-
공용컴포넌트 분리! Input하나만 작업해봤는데 Button이나 Select도 분리해보고 싶다.
-
장바구니에서 쿠폰 적용시
쿠폰 생성버튼의 기능이 없던데 이 부분도 추가해보고 싶다. 장바구니 테스트하면서 신경쓰여 죽는줄... PR회고 쓰면서 느낀건데 오버엔지니어링이랍시고 만들어볼걸 그랬나 흠 🤔
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
-
Jotai만으로만 리팩토링을 진행하였으며 아직 어느정도의 props drilling은 있는 상황입니다. props drilling으로 전달하고 있는 값들이 App에서 다른 값들에 의존되어있는 경우이거나 함수인 경우가 많은데 이런 경우도 모두 순수함수나 hooks로 분리해야했을까요?
-
코멘트 부분을 개선한다면 어떻게 개선할 수 있을까요? 처음에는 단순히 이벤트 헨들러로 분리할까 했지만 그렇게 해서는 해결이 안될거라 판단되어 보류하였습니다.
과제 피드백
아주 잘했어요 가은. 뭐랄까 아직 다 완성을 안해서 결과는 아쉬운 점이 보이지만 나쁜 습관없이 했기 때문에 이대로 끝까지 했을때의 결과가 기대되는 그런 모양새입니다. 본인이 하나씩 생각하면서 해서 그런걸수도 있고 너무 개념적으로 deep하게 접근해서 오버엔지니어링의 함정에 빠지지 않았네요.
순수함수는 모든 인자를 명시적으로 외부에 위임을 하기 때문에 테스트가 쉬워지고 독립성을 유지합니다. 하지만 이렇게 만들었다가는 컴포넌트나 상태를 관리하는데 props의 지옥을 맛보죠. 인자가 1-2여야지 이렇게 많아서는 관리가 어렵습니다. 그래서 인자를 생략하거나 내부에서만 접근할 수 있는 상태를 만들어서 DX를 향상하죠.
어찌보면 이 둘이 양립하는게 어려워 보일 수 있습니다. 둘의 지향점과 컨셉은 완전히 다르니까요. 하지만 코드는 하나의 관점만 가지고 작성하는게 아니니까 어떤 관점의 코드가 더 잘 어울리는지는 맥락에 따라 다릅니다. 우리는 여러개의 도구와 관점을 가지고 적재적소에 쓰는 법이 중요합니다. 숟가락과 젓가락의 쓰임이 다른 것 처럼 말이죠.
끝까지는 해봤으면 좋았겠다 싶네요. 그러면 뭔가 더 확실히 알았을지도 모르겠습니다. 코멘트에 적어둔 뭔가 해볼 수 있을 것 같은데? 라고 느꼈는데 완벽한 뭔가를 떠올리려고 하지 말고 지금보다 더 나아지는 방향으로는 개선을 해보면 좋을거에요. 일단 핸들러라도 밖으로 꺼내보는건 어떘을까요? 그리고 굳이 지금의 코드에서 문제가 없다면 과한 분리는 오히려 복잡도를 높일 뿐이에요.
"... 해결이 안될거라 판단되어 보류하였습니다." 라는 건 좋은 판단입니다. 그렇지만 "...처음에는 단순히 이벤트 헨들러로 분리할까 했지만 ..." 이라고 생각한것도 실행한것도 좋았을 것 같아요. 완벽이 아니라 지금보다 더 나아지는게 중요하니까요.
수고했습니다. 아쉬움과 인사이트는 6주차 과제에서 꼭 결자해지 해보기를 바래요! 수고하셨습니다.