-
Notifications
You must be signed in to change notification settings - Fork 50
[5팀 진재윤] Chapter2-1. 프레임워크 없이 SPA 만들기 #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
JunilHwang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 피드백은 n8n + ai (gpt-5-mini)를 활용하여 자동으로 생성된 내용입니다.
전체 리뷰 요약
이번 PR은 요구사항에 맞춰 상품 목록, 상세, 장바구니, 검색 및 필터, 무한 스크롤 등 핵심 기능을 충실히 구현하였으며, Mock API 서버와 MSW를 사용해 테스트 가능하도록 구성했습니다. 컴포넌트를 잘 분리하여 역할 분담이 이루어져 있고, SPA 라우팅 관리도 적절히 수행되고 있습니다.
다만, 단일 파일(main.js) 내에 렌더링, 상태 관리, URL parsing/동기화, 이벤트 핸들링이 복합적으로 얽혀 있어 향후 기능 추가나 유지보수 시 복잡도가 증가할 수 있는 점은 개선이 필요합니다.
현재 코드 구조
- main.js가 렌더링과 이벤트 핸들링, 상태 관리(로컬스토리지 및 URL Parsing) 대부분을 담당
- API 통신과 뷰 렌더링, 컴포넌트가 명확히 분리되어 비교적 이해하기 쉬움
- 무한 스크롤, 장바구니 상태 관리 로직이 utils 내 별도 파일로 분리됨
- UI 컴포넌트들은 태그 스트링 템플릿 기반으로 최소한의 리턴 단위로 구현됨
설계 및 확장성 피드백
- 상태 관리 및 URL 동기화 로직을 별도 모듈이나 상태 컨테이너로 분리하면 유지보수 및 확장성이 크게 향상됩니다.
- 렌더링과 로직 결합도를 줄이고 컴포넌트 리렌더링 최적화 기법을 도입할 여지가 있습니다.
- 이벤트 핸들러를 좀 더 작고 역할별로 분리하여 가독성을 높이는 방안을 권장합니다.
- 상품 상세 페이지, 장바구니 등 UI 컴포넌트 내부 SNS나 SVG 등을 컴포넌트 단위로 더 분리하면 재사용성과 테스트 편리성이 좋아집니다.
전반적으로 요구사항에 잘 부합하며, 다음 단계에서는 상태 관리, 컴포넌트 설계 패턴 및 효율적인 DOM 업데이트 방법에 집중해보시면 좋겠습니다.PR에 문의 주신 추가 질문이 없어, 전체적인 코드 이해를 기반으로 심도있게 답변 드립니다.
상태 관리와 URL 동기화
현 코드에서는 URL 쿼리 파라미터를 직접 읽어 렌더링 시 상태로 활용하는 방식을 차용하고 있습니다. 이런 방식은 절대적인 단일 진실 소스로서 URL을 사용한다는 장점이 있지만, 앱 상태가 복잡해질수록 URL과 내부 메모리 상태의 불일치 가능성, 이벤트 중복 처리, 로직 분산 등 유지보수 비용 증가 요인도 함께 발생합니다.
실제 확장 가능성을 고려할 때는 중간 상태 객체(State Store)를 두고, URL 파라미터->상태 변환, 상태->URL 업데이트를 엄격히 컨트롤하는 구조를 도입하는 것이 좋습니다. 이를 통해 앱 내 모든 상태 변화를 중앙 집중식으로 추적 및 관리할 수 있고, 변경되는 부분만 렌더링해 성능 향상도 가능합니다.
DOM 이벤트 처리
본 코드의 이벤트 위임 방식은 매우 효율적인데, 이벤트 핸들러가 점점 길어지는 것을 여러 작은 함수로 분할하면 가독성과 유지보수성이 크게 올라갑니다. 예를 들어 "장바구니 수량 증가" 관련 이벤트 핸들러를 별도 함수로 분리한 후, 이벤트 내에서는 해당 함수 호출만 수행하는 식입니다.
UI 컴포넌트 디자인
컴포넌트들이 순수 템플릿 문자열로 구성되어 있어 프레임워크 없이도 동작하지만, 관리 포인트가 많아지면 컴포넌트별로 독립된 상태와 인터랙션을 갖기 어려울 수 있습니다. 따라서 컴포넌트 단위로 이벤트 바인딩, 상태 관리 책임 분리를 차차 도입해 나가는 것도 고려해보세요.
토스트 및 모달 관리
여러 토스트가 연속해서 나타나는 시나리오, 혹은 다양한 타입별 메시지 처리 로직이 필요하다면 토스트 생성 큐(queue) 관리, 모달 상태 관리 방식 등을 별도로 구현하는 것도 추천됩니다.
API 모킹 및 테스트
Mock Service Worker 설정과 Playwright 테스트가 잘 통합되어 있어 신뢰할 수 있는 테스트 커버리지가 확보되어 있습니다. 실제 API 환경과 차이가 있을 수 있으므로 모킹 데이터 업데이트 주기에 신경 써야 합니다.
이상입니다. 기능 구현 과정과 테스트 모두 잘 이루어졌으니, 다음 스텝으로 상태 관리 아키텍처와 UI 컴포넌트 분리 및 최적화 작업에 집중해보시면 더 좋은 코드 품질을 경험할 수 있을 것입니다. 언제든 추가 질문 주시면 상세히 도와드리겠습니다.
| .then(([productsData, categoriesData]) => { | ||
| const { products, pagination } = productsData; | ||
| const { filters } = pageState; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. 문제 상황 제시
사이트가 성장하며 상품 목록에 다양한 조건(검색어, 카테고리, 정렬, 페이지 수 등)이 증가할 경우, render 함수가 복잡한 URL 파라미터 읽기 및 비교 작업과 상품 목록 API 요청 및 렌더링까지 모두 담당하고 있음.
현재 코드의 한계:
render함수가 너무 많은 책임을 가지고 있어 유지보수가 어려움- URL 파라미터 및 상태가
render함수 내부에 산재되어 재사용과 확장이 어렵다 - 무한 스크롤 페이지네이션 로직이
initInfiniteScroll함수에 분리되어 있지만, 전체 상태 관리 및 UI 업데이트가 명확하게 분리되지 않아 확장성 저하
2. 근본 원인
핵심 문제: 컴포넌트 렌더링, 상태 관리, URL 파라미터 읽기/쓰기, API 요청이 한 함수 또는 몇몇 함수에 집중되어 있어 관심사가 분리되어 있지 않음
왜 문제인가:
이 구조는 새로운 요구사항(예: 카테고리 추가, 필터 추가, SEO 최적화, SSR 대응 등)이 발생하면 한 함수의 수정이 매우 복잡해지고, 테스트와 디버깅도 어려워짐.
3. 개선 구조
현재 구조:
render 함수 내에서 URL 파라미터 읽기 → API 호출 → 렌더링 결과를 출려 → 이벤트에 따른 URL push 및 재렌더링
개선된 구조:
- URL 상태 관리 추상화: URL과 상태를 1:1 매핑하는 별도 모듈로 분리
- 상태 관리와 데이터 페칭 분리: 상태 변화에 따른 API 호출과 데이터 변화를 별도의 책임으로 분리
- 컴포넌트별 렌더링 분리: 상태 변화에 따라 필요한 컴포넌트만 재렌더링하도록 분리
- 무한 스크롤과 상태 관리를 더 유기적으로 연결하고, 상태 변화에 따라 무한 스크롤 초기화 및 파기 관리
개선 사항:
- URL 파라미터를 읽어 상태를 만들고, 반대로 상태 변경 시 URL을 업데이트하는 유틸리티 함수 작성
- 상태 관리를 전역 객체 또는 상태 관리 라이브러리 도입 (간단한 경우에선 옵저버 패턴 적용 가능)
- 렌더링 함수들은 상태를 받아 UI만 담당하도록 설계
- 이벤트 핸들러에서는 상태를 변경하고, 상태 변경 시 렌더링 함수가 호출되는 흐름
코드 비교:
// ❌ 현재: render 함수가 모든 역할 수행
async function render() {
const url = new URL(window.location);
const limit = parseInt(url.searchParams.get('limit')) || 20;
const page = parseInt(url.searchParams.get('current')) || 1;
const search = url.searchParams.get('search') || '';
...
// API 호출 및 렌더링, 무한 스크롤 초기화 등
}
// ✅ 개선된 방식 예시 (의사코드)
const state = { limit: 20, page: 1, search: '', category1: '', category2: '', sort: 'price_asc' };
function syncStateAndURL() {
// URL -> 상태 읽기 또는 상태 -> URL 쓰기 구현
}
async function fetchProducts() {
// API 호출만 담당
return getProducts(state);
}
async function renderProducts() {
const products = await fetchProducts();
ProductList.render(products);
}
function onStateChange() {
syncStateAndURL();
renderProducts();
initInfiniteScroll(state);
}
// 이벤트 핸들러에서는 상태만 변경
function onFilterChange(newFilters) {
Object.assign(state, newFilters);
onStateChange();
}10가지 추가 범용 피드백
- 이벤트 위임 사용은 좋으나 이벤트 핸들러 로직이 너무 길어 가독성과 유지보수성 저하
- 여러 요소를 여러 번 쿼리하는 부분에 변수 할당 후 재사용하면 성능과 가독성 개선 가능
- 하드코딩된 문자열(URL path, CSS class 등)이 많으므로 enum 또는 상수로 분리하는 것이 좋음
- 중복 코드(특히 모달 열기/닫기, 토스트 알림 생성 등)는 함수화 또는 컴포넌트화 권장
- 토스트 메시지와 모달은 UI 상태 관리 시스템 도입 시 분리하여 관리하면 더 효율적
- 가격 등을 숫자 처리할 때 타입 안정성을 위해 parseInt/Number 변환 시 예외 처리 검토
- 컴포넌트가 많은데, 복잡도 때문에 파일명과 folder 구조를 더 체계적으로 관리하면 좋음
- CSS 클래스를 Tailwind와 함께 사용하고 있는데, utility 클래스 중복 최소화를 위해 공통 스타일 분리 고려
- API 호출 실패 시 상세 에러 처리가 부족하며 사용자에게 적절히 안내하는 로직 강화 필요
- localStorage 관련 작업은 JSON 파싱/스트링화 시 실패 케이스 핸들링과 동시성 문제 고려 필요
| if ($target.closest('.breadcrumb-link')) { | ||
| e.stopPropagation(); | ||
| const button = $target.closest('.breadcrumb-link'); | ||
| const category1 = button.dataset.category1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. 문제 상황 제시
현재 장바구니 추가, 수량 변경 및 삭제 등 기능들이 utils/cart.js와 main.js에 중복 연결되어 있지만,
상태 동기화 및 UI 업데이트가 일관되게 처리되지 않아 사용자 경험에 영향이 있을 수 있음.
현재 코드의 한계:
- 장바구니에 추가/수량 변경 시 모달 새로고침과 헤더 배지 업데이트가 산발적으로 호출됨
- UI 리렌더링과 데이터 상태 관리가 분리되어 있어 변경 시 UI가 늦게 반영되거나 누락될 가능성 존재
2. 근본 원인
비즈니스 로직(상태 변경)과 UI 갱신(렌더링)이 명확하게 분리되지 않고 서로 의존되어 있어, 상태 변경 시 자동 UI 갱신 메커니즘이 부족함.
3. 개선 구조
- 상태 변경 함수들이 이벤트 핸들러 내에서 직접 UI를 갱신하지 않고, 상태 변경 후 별도의 UI 갱신 함수가 호출되도록 변경
- Pub/Sub 또는 옵저버 패턴을 도입하여 상태 변경 시 자동으로 관련 UI 컴포넌트가 업데이트되도록 개선
코드 비교:
// ❌ 현재 모든 핸들러에서 updateHeader(); refreshCartModal(); showToast() 직접 호출
addToCart(product, quantity);
updateHeader();
refreshCartModal();
// ✅ 개선
function onCartChange() {
updateHeader();
refreshCartModal();
}
const addProductToCart = (product, quantity) => {
addToCart(product, quantity);
onCartChange();
};| const category1 = $target.dataset.category1; | ||
| const url = new URL(window.location); | ||
| url.searchParams.set('category1', category1); | ||
| url.searchParams.delete('category2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3. 문제 상황 제시
이벤트 핸들러 내에서 dom 쿼리 및 데이터 추출, 상태 변경, UI 변경, URL 관리 등이 밀접하게 섞여 있어, 코드 가독성과 유지보수성이 떨어짐.
현재 코드의 한계:
- 개별 이벤트 핸들러가 매우 크고 많은 행위를 수행, 로직 분리 필요
- 재사용성 낮고 테스트가 어려움
3. 근본 원인
관심사의 분리가 미흡하여, 한 함수에 여러 역할이 혼재되어 있음.
3. 개선 구조
- 이벤트 핸들러에서 DOM 조작 코드를 최소화하고, 상태 관리 함수와 UI 렌더링 함수를 각각 호출
- 상태 변경과 URL 동기화를 별도의 모듈로 분리하여 관리
- 이벤트 위임 시 클릭된 요소에 따른 핸들링만 수행하고, 상태 변경은 별도의 함수 호출로 외부 분리
코드 비교:
// ❌ 현재 방식
if ($target.closest('.quantity-increase-btn')) {
const productId = $target.dataset.productId;
const item = getCartData().items.find(item => item.id === productId);
updateCartQuantity(productId, item.quantity + 1);
refreshCartModal();
}
// ✅ 개선
function onQuantityIncrease(productId) {
updateCartQuantity(productId, getCartQuantity(productId) + 1);
refreshCartModal();
}
// 이벤트 핸들러 내
if ($target.closest('.quantity-increase-btn')) {
const productId = $target.dataset.productId;
onQuantityIncrease(productId);
}| `; | ||
|
|
||
| export const Rating = ({ rating }) => { | ||
| return /* HTML */ ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4. 문제 상황 제시
Rating 컴포넌트가 rating 값을 소수점 포함한 실수로 받음에도 fullStar, emptyStar 반복을 정수로만 처리하고 있어 소숫점 반영이 누락되는 문제 발생 가능
현재 코드의 한계:
- 별점이 4.5일 경우, 4개만 꽉 찬 별표가 표시되고 반쪽 별 등 세밀한 표현 불가
4. 근본 원인
단순 정수 카운트만 사용해 별점 UI가 표현되어, 다양한 평점 표현 확장성 부족
4. 개선 사항
- 정수, 반쪽 별(half star) 처리 포함한 UI 개선
- 별점 5개 중 몇 개가 full, half, empty인지 계산해서 출력
코드 비교:
// ❌ 현재 방식
${fullStar.repeat(rating)} ${emptyStar.repeat(5 - rating)}
// ✅ 개선된 방식 (반별점 처리)
const fullStars = Math.floor(rating);
const halfStar = rating - fullStars >= 0.5;
const emptyStars = 5 - fullStars - (halfStar ? 1 : 0);
return /* HTML */ `
<div class="flex items-center">
${fullStar.repeat(fullStars)}
${halfStar ? halfStarSVG : ''}
${emptyStar.repeat(emptyStars)}
</div>
`;| <path | ||
| stroke-linecap="round" | ||
| stroke-linejoin="round" | ||
| stroke-width="2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5. 문제 상황 제시
토스트 팝업 닫기 버튼 클릭과 자동 사라지는 타이머가 있음에도, 동시에 여러 토스트 등장 시 관리가 복잡하며,
토스트가 계속 생성될 경우 DOM 누적 우려 있음
현재 코드의 한계:
- 토스트 생성 시 인스턴스 관리 미흡으로 여러 토스트 간의 충돌이나 메모리 누수 가능성
5. 근본 원인
토스트 요소가 DOM에 계속 쌓이며, 제거 시기 관리가 자동 타이머와 수동 닫기가 별도임
5. 개선 사항
- 토스트 팝업을 큐로 관리하여 동시 생성 개수를 제한하거나 뒤로 밀림 현상 완화
- 개별 토스트 구성요소에 고유 id 할당 및 상태 관리
- 필요 시 토스트 컴포넌트 클래스로 리팩토링 권장
| // 로딩 UI 숨김 | ||
| const $loadingEl = document.querySelector('#infinite-scroll-loading'); | ||
| if ($loadingEl) $loadingEl.style.display = 'none'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
6. 문제 상황 제시
무한 스크롤 구현 시 Intersection Observer를 사용하여 잘 동작하지만,
상태 객체의 직접 수정을 여러 곳에서 수행하여 복잡도가 오를 수 있음
현재 코드의 한계:
- 상태 관리의 일관성이 부족하며 필요한 부분과 상태 값을 명확히 분리하는 구조가 아님
- 예외 상황(네트워크 오류, 스크롤 중복 호출 등) 핸들링 미흡
6. 개선 사항
- 상태 변경은 setState 함수로 제한하여 사이드 이펙트를 방지
- 네트워크 오류, 더 이상 데이터 없을 때 UI 적절히 알림
- intersection observer 해제와 상태 초기화를 명확히 구분하여 메모리 누수 방지
| > | ||
| ${cat1} | ||
| </button> | ||
| `, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7. 문제 상황 제시
카테고리 버튼 컴포넌트에서 1depth와 2depth 카테고리를 렌더링하지만,
버튼의 선택 상태 표시를 위한 스타일 조건이 복잡하게 하드코딩 되어 있음
현재 코드의 한계:
- 조건문 inline으로 작성되어 코드 가독성 떨어짐
- UI 변화 시 스타일 관리 복잡
7. 개선 사항
- 선택된 카테고리 상태를 별 변수에 미리 할당해 가독성 개선
- 조건부 CSS 적용을 함수나 라이브러리로 분리하면 유지보수 편리
| viewBox="0 0 24 24" | ||
| > | ||
| <path | ||
| stroke-linecap="round" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8. 문제 상황 제시
브레드크럼 구조가 2depth까지만 지원하는 듯 보이나,
차후 3depth 이상 확장 필요시 현재 구조는 하드코딩되어 확장성 낮음
현재 코드의 한계:
- 신규 깊은 카테고리(depth) 추가 시 컴포넌트 전체 수정 필요
8. 개선 사항
- 카테고리 배열 경로를 받아 map 렌더링하는 재귀적 혹은 반복적 컴포넌트 설계 권장
- 유동적인 깊이 지원으로 UI 확장성 확보 가능
| ></path> | ||
| </svg> | ||
| `; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9. 문제 상황 제시
스타 SVG 아이콘들이 컴포넌트 상단에 하드코딩되어 있어 코드 분량이 길고, 재사용성이 떨어짐
현재 코드의 한계:
- 아이콘 변경, 스타일 조정시 SVG 코드를 직접 수정해야 함
9. 개선 사항
- SVG 컴포넌트를 별도 파일이나 React, Vue 등 컴포넌트 형태로 분리 추천
- CSS 색상 변경 시 클래스 조작하거나 스타일 프롭 활용 권장
| category2, | ||
| category3, | ||
| category4, | ||
| sort, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
10. 문제 상황 제시
API delay 함수가 고정 값으로 되어 있어 상황에 따라 다양한 지연 시뮬레이션이 어렵고, 테스트 유연성이 제한
현재 코드의 한계:
- 모든 API 응답에서 200ms 딜레이만 사용됨
10. 개선 사항
- delay 함수를 매개변수로 지연 시간 설정 가능하게 개선하면 테스트 목적에 따라 활용도 증가
const delay = (ms = 200) => new Promise(resolve => setTimeout(resolve, ms));
eveneul
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
재윤 님! 이번 주차도 고생 많으셨습니다~ 자바스크립트 챕터라고 했지만 생각보다 어마무시한 난이도에 많이 당황하셨죠? ㅎㅎ 그래도 잘 해내셨네요!
main.js에 여러 기능들이 있는데, 이걸 분리하면 더 좋을 것 같습니다!
다음 주도 같이 파이팅해 봅시다~~!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Category 버튼을 1뎁스, 2뎁스를 따로따로 만드는 것보다는 하나의 컴포넌트로 합치는 건 어떨까요? 저도 이번 챕터 문제를 풀 때 똑같은 고민을 했다가 재윤 님처럼 두 개로 나눈 경험이 있는데요, 지금 생각해 보면 카테고리 뎁스가 더 많아질 때는 어떻게 하지? 생각이 들어서요.
제가 생각한 코드는..
const Category = (attrs, label, isSelected) => /* HTML */ `
<button
${Object.entries(attrs)
.map((k, v) => `data-${k}=${v}`)
.join(" ")}
class="px-3 py-2 text-sm rounded-md border transition-color
${isSelected ? "bg-blue-100 border-blue-300 text-blue-800" : "bg-white border-gray-300 text-gray-700 hover:bg-gray-50"}
>${label}</button>
`;이렇게 만들고, Category 컴포넌트를 사용할 때는,
.map(product => Category({ category1: product }, product, false).map(product => Category({ category1: selectedCategory1, category2: product },
product,
product === selectedCategory2
)
)이런 식으로 해도 좋을 것 같아용
| error: 'bg-red-600', | ||
| }; | ||
|
|
||
| const icons = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토스트 ui가 상태별로 나뉘어지는데, 요렇게 나누신 거 좋다고 생각합니다!
| requestAnimationFrame(() => { | ||
| toast.style.opacity = '1'; | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
requestAnimation을 두 번 중첩해서 쓰는 것보다 opacity 0과 transition 설정은 기존 Toast 컴포넌트 CSS로 박고 opacity 1, 0으로 하는 건 어떨까요! requestAnimationFrame을 두 번 쓰신 이유가 궁금해용
| }; | ||
|
|
||
| // 장바구니 모달 닫기 | ||
| const closeCartModal = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 main.js에 장바구니 관련 기능, render 관련 기능 등.. 여러 독립적인 기능들이 한꺼번에 모여 있어 한 파일 속 코드의 길이가 너무 긴 것 같아요! /core/ 같은 파일을 만들어서 각각 분리해 놓으면 어떨까요?
과제 체크포인트
배포 링크
https://jy0813.github.io/front_7th_chapter2-1/
기본과제
상품목록
상품 목록 로딩
상품 목록 조회
한 페이지에 보여질 상품 수 선택
상품 정렬 기능
무한 스크롤 페이지네이션
상품을 장바구니에 담기
상품 검색
카테고리 선택
카테고리 네비게이션
현재 상품 수 표시
장바구니
장바구니 모달
장바구니 수량 조절
장바구니 삭제
장바구니 선택 삭제
장바구니 전체 선택
장바구니 비우기
상품 상세
상품 클릭시 상세 페이지 이동
/product/{productId}형태로 변경된다상품 상세 페이지 기능
상품 상세 - 장바구니 담기
관련 상품 기능
상품 상세 페이지 내 네비게이션
사용자 피드백 시스템
토스트 메시지
심화과제
SPA 네비게이션 및 URL 관리
페이지 이동
상품 목록 - URL 쿼리 반영
상품 목록 - 새로고침 시 상태 유지
장바구니 - 새로고침 시 데이터 유지
상품 상세 - URL에 ID 반영
/product/{productId})상품 상세 - 새로고침시 유지
404 페이지
AI로 한 번 더 구현하기
과제 셀프회고
사실 이번 과제를 하면서 가장 크게 느낀 건, 우리가 일상처럼 쓰고 있는 SPA라는 기술이 얼마나 거대한 구조물 위에서 돌아가고 있는지였습니다.
평소 아무렇지 않게 쓰던 useState와 useEffect 내부 동작을 직접 구현해보면서, React에서는 setState를 호출하면 화면이 알아서 좋은 타이밍에 갱신되고, useEffect는 적당히 알아서 부수효과를 수행해주니까 그냥 편하게 쓰기만 하면 됐는데, 막상 비슷한 기능을 스스로 만들어보려고 하니 너무나도 복잡하게 느껴졌습니다.
상태를 바꾸기만 하면 되는 게 아니라, 어떤 컴포넌트가 이 상태를 구독하는지, 언제 렌더링을 트리거해야 하는지, 이전 상태와 비교해서 실제로 갱신할 필요가 있는지, 렌더링 우선순위는 어떤지, 렌더 순서가 뒤집히면 어떻게 안정성을 보장할지 이런 모든 문제들을 직접 마주하였습니다.
React는 이런 것들을 Fiber 아키텍처라는 괴물 같은 구조로 해결하고 있습니다. Fiber는 UI를 작은 단위의 작업 으로 쪼개서 스케줄링하고, 우선순위를 부여하고, 필요할 때는 렌더링을 미루기도 하고, 중단했다 다시 이어서 처리하기도 합니다.
Fiber는 모든 컴포넌트에 작업 단위를 부여하고, 그 단위들을 마치 운영체제의 프로세스처럼 관리합니다.
이 구조가 있으니까 useEffect가 mount → update → cleanup → unmount 로 나뉘어 실행될 수 있는 거고, setState가 여러 번 호출돼도 적절히 묶어서 한 번에 처리될 수 있던 걸 아마 이번 과제를 통해서 useState 와 useEffect 의 구현 시도를 해보셨다면 다들 느끼셨을 거 같습니다.
또 React는 DOM을 매번 갈아엎는 게 아니라, Reconciliation이라는 비교 알고리즘을 통해 딱 필요한 부분만 업데이트합니다.
이 알고리즘을 직접 구현해보면 좋았겠지만 우선은 과제 통과에 시간을 많이들여서 시도해보지는 못했습니다.
그래도 Reconciliation으로 가상 DOM이 실제 DOM보다 빠르다게 이래서 그랬구나 하고 간접적으로 느껴봤습니다.
SPA를 직접 구현해보니, 라우팅, 상태 관리, DOM 업데이트, 비동기 처리, 최적화 등..
React와 Next가 그동안 조용히 대신해주고 있던 작업들이 얼마나 많았는지 체감하였습니다.
그러다 보니 이번 과제는 단순히 기능을 만드는 시간이 아니라, 우리가 당연하게 쓰는 기술이 실제로 어떤 원리를 품고 있는지 직접 들여다보는 시간을 준거 같아서 매우 만족하고있습니다. 기능 구현과 과제 통과에 많은 시간을 들였지만 이번 과제가 아니였다면 평소에 얻지 못했을 깨달음이어서 더욱 값진 시간이었습니다.
기술적 성장
React Fiber의 필요성
Hooks 규칙의 이유
Store vs Component State
렌더링의 복잡성
import.meta.env.MODE 와 process.env.NODE_ENV 의 차이
자랑하고 싶은 코드
개선이 필요하다고 생각하는 코드
전체적으로 추상화가 부족하고 main에 몰려있는 상태여서 리팩토링을 전체적으로 진행해야합니다.
학습 효과 분석
가장 큰 배움이 있었던 부분
사실 완벽히 이해했다기보다는 이제는 그냥 사용하기보다는 이해하려 시도하는 첫발을 내디뎠다 생각합니다.
"직접 만들어보니 React가 왜 이렇게 설계되었는지 엿볼 수 있었다."
코드를 사용하는 것과 코드를 이해하는 것의 차이를 체감했습니다.
추가 학습이 필요한 영역
Fiber 구조 공부
Reconciliation 알고리즘
Priority Queue
Time Slicing
Virtual DOM 구현
diff 알고리즘
patch 최적화
과제 피드백
이번 과제가 좋았던 이유는 단순히 동작하는 SPA를 만들었다는 성취감보다도, 그 과정에서 라우팅, 상태 관리, 렌더링 사이클이라는 근본적인 영역들을 직접 만져볼 수 있었다는 점입니다.
AI 활용 경험 공유하기
AI로 "직접 만들어보면 이해할 수 있지 않을까?" 라는 생각으로 시작했습니다.
개인적으로 프로젝트를 생성하여 같은 환경을 구성하여 작업하였습니다.
아쉬운건..직접 만들고 경험하면서 아 잘못만들었다~하고 히스토리를 제대로 남겨놓지 않아서
PR에 경험 공유에 대한 내용이 많이 유실된거같아 아쉬운 마음입니다.
React를 사용하면서 늘 궁금했던 것들이 있는데,
1단계
2단계
cleanup은 다음 effect 실행 전에 실행됨!
Observer 패턴 Router 구현을 구현하면서 멘토링 시간과 평일 QA에 본걸 기반으로 만들어보려했는데 이해를 못했습니다..
사실 notify를 제안한 클로드한테 아래 처럼 물어봤는데도..아니 구독자들한테 알림을 주면 뭐가 좋은데? 하면서
물어봤지만 그래도..명확히 해결을 못했습니다.
Q: subscribe/notify가 뭐야?
A: 유튜브 구독 개념!
Router 에 Quuer 파라미터 구현도 같이 하기
그리고 다시 api 요청을 하고 확인하던 중 문제를 발견해서 고쳐보기도하고..
useEffect가 실행 안 됨 (부분 렌더링 시도)
시도한 것
문제: useEffect hooks이 재실행되지 않음 (Counter 함수를 다시 안 불러서)
문제: Query 변경 시 UI 업데이트 안 됨
증상: 검색, 카테고리 변경해도 화면 그대로
해결: currentPath에 query까지 저장되어 라우팅 로직 망가지는거같아 URL 파싱해서 pathname만 추출하여 사용했습니다.
그리고 진짜 크리티컬한 문제들은..렌더링이 3번 일어나는데..useState 에서 문제를 찾았습니다.
클로드와 찾아본 문제의 원인은 아래 코드라는데 음..
라는 깨달음을 얻고..
전역 Hooks 인스턴스 = Store? 라는 문제가 일어났습니다..useState가
라는 문제로 useState 구현을 완전이 잘못했다...라고 생각하고 useEffect와 useState 방식으로 본 과제의 리팩토링은 못했습니다.
그래도 이러한 문제들도,
Fiber 아키텍처를 공부해야하고. Fiber 아키텍처가
가능하다는걸 깨달았습니다. Fiber 에 대해 항상 들어보긴 했지만 스윽 읽고 넘어가고 했었는데 결국 이렇게 공부하는 날이 왔습니다.
이번에는 그래도 실패했지만,,다음주에 다룬다고 들었던거같은데 다시 한번 츄라이 해보겠습니다.
그리고 아...잘못만들었네 하고 클로드한테 하소연 하니까
React 역사의 필연성
시행착오:
우리의 여정 = React의 역사를 경험했다. 라고 위로해주던데 이번 과제 덕분에 이런거도 해보고 조금 더 고민해보고 생각하는 법을 배운거같습니다. 근데 이게 AI 활용경험 공유가 맞나..?우선 실패한 경험입니다. 🤣
리뷰 받고 싶은 내용