튜토리얼 : 장바구니 만들기
이 튜토리얼에서는 React 환경에서 caro-kann 라이브러리를 활용하여 간단하면서도 실용적인 장바구니 기능을 구현하는 과정을 안내합니다. caro-kann이 제공하는 직관적인 상태 관리 방식과 강력한 미들웨어 시스템을 통해 어떻게 효율적으로 상태를 다루고, 다양한 부가 기능을 손쉽게 통합할 수 있는지 배우게 됩니다.
이 튜토리얼은 React에 대한 기본적인 이해가 있는 개발자를 대상으로 하며, caro-kann의 핵심 개념과 실제 사용법을 익혀 실제 프로젝트에 적용할 수 있도록 돕는 것을 목표로 합니다.
store 만들기#
애플리케이션의 상태 관리를 시작하기 전에, 먼저 우리가 다룰 데이터의 구조를 정의하는 것이 좋습니다. 여기서는 장바구니에 담길 '제품(Product)'의 속성을 명확히 하기 위해 TypeScript 타입을 생성합니다. 각 제품은 고유 id, name, price를 필수적으로 가지며, 선택적으로 imageUrl과 description을 포함할 수 있도록 정의합니다. 이렇게 타입을 미리 정의하면 개발 과정에서 발생할 수 있는 데이터 관련 오류를 줄이고 코드의 가독성과 유지보수성을 높일 수 있습니다.
다음으로, caro-kann 라이브러리에서 create 함수를 가져와 전역 상태 저장소(store)를 생성합니다. create 함수는 초기 상태값을 인자로 받아, 해당 상태를 관리할 수 있는 커스텀 훅(hook)을 반환합니다. 이 예제에서는 빈 배열 []을 초기 상태로 설정하여, 처음에는 장바구니가 비어있음을 나타냅니다. create<Array<Product>>([]) 호출 결과로 반환된 useCart 훅은 이제 우리 애플리케이션의 모든 컴포넌트에서 장바구니 상태에 접근하고 이를 변경하는 데 사용될 핵심 인터페이스가 됩니다.
useCart로 전역 상태 변경하기#
이제 생성한 useCart 훅을 사용하여 실제 컴포넌트에서 장바구니 상태를 변경하는 방법을 살펴보겠습니다.
먼저 ProductBinder 컴포넌트는 서버로부터 받아온 제품 목록(productList)을 props로 받아, 각 제품에 대해 ProductCard 컴포넌트를 렌더링하는 역할을 합니다. 이는 일종의 리스트 렌더러로, 여러 제품 정보를 화면에 표시하기 위한 컨테이너 역할을 수행합니다.
이 컴포넌트 내부에서 useCart() 훅을 호출하면, React의 useState 훅과 유사하게 현재 장바구니 상태(cart)와 상태를 변경할 수 있는 함수(setCart)를 담은 튜플을 반환받습니다.
'Add to Cart' 버튼의 onClick 핸들러에서는 setCart 함수를 사용합니다. 이때, 이전 상태(prev)를 받아 새로운 제품(product)을 배열의 끝에 추가한 새로운 배열을 반환하는 함수형 업데이트 방식을 사용합니다 (setCart(prev => [...prev, product])). 이는 상태의 불변성을 유지하는 좋은 패턴이며, React가 변경 사항을 감지하고 UI를 효율적으로 업데이트하는 데 도움을 줍니다.
하지만 ProductBinder 컴포넌트의 현재 구현을 보면, 장바구니의 내용을 화면에 표시하지는 않습니다. 즉, cart 상태값을 직접 읽을 필요는 없고, 오직 setCart함수를 통해 장바구니에 아이템을 추가하는 기능만 수행합니다. 이런 경우, 컴포넌트가 장바구니 상태가 변경될 때마다 불필요하게 리렌더링되는 것을 방지하기 위해 최적화를 고려할 수 있습니다.
caro-kann은 이러한 상황을 위해 useCart 훅에 내장된 writeOnly 메서드를 제공합니다. useCart.writeOnly()를 사용하면 상태 값 자체는 가져오지 않고, 오직 상태를 변경하는 함수(setCart에 해당)만을 반환받습니다.
이를 통해 ProductBinder 컴포넌트는 장바구니의 현재 상태 변화에는 구독하지 않게 되어, 오직 제품을 추가하는 역할만 수행하면서도 불필요한 리렌더링을 피할 수 있습니다. 이는 특히 상태 변경이 잦거나 많은 컴포넌트가 동일한 상태를 공유할 때 성능 향상에 기여할 수 있는 유용한 최적화 기법입니다.
readOnly 메서드로 파생 상태 관리하기#
애플리케이션의 헤더(Header) 컴포넌트는 사용자에게 현재 장바구니에 담긴 제품의 총 개수를 시각적으로 보여주는 중요한 역할을 합니다. 이 기능을 구현하기 위해 useCart 훅의 readOnly 메서드를 활용합니다.readOnly 메서드는 상태 저장소의 현재 값에 접근하여 원하는 데이터를 추출하거나 계산할 수 있게 해주는 강력한 기능입니다. 이 메서드는 콜백 함수를 인자로 받으며, 이 콜백 함수는 현재 상태(store)를 매개변수로 받아 파생된 값을 반환합니다.
아래 예시에서는 useCart.readOnly(store => store.length)와 같이 사용하여, 장바구니 배열(store)의 length 프로퍼티를 통해 담긴 제품의 총 개수(cartLength)를 계산합니다.
readOnly를 사용하는 주된 이점은 다음과 같습니다.
- 선택적 구독 (Selective Subscription): 컴포넌트는 전체 상태 객체가 아닌, 콜백 함수가 반환하는 특정 값의 변경에만 반응하여 리렌더링됩니다. 예를 들어, 장바구니에 제품이 추가되거나 삭제되어
store.length가 변경될 때만Header컴포넌트가 리렌더링됩니다. 만약 장바구니 내의 특정 아이템의 속성만 변경되고 전체 개수는 그대로라면, 이 컴포넌트는 리렌더링되지 않아 불필요한 연산을 줄일 수 있습니다. - 파생 상태 계산: 단순히 상태의 일부를 가져오는 것뿐만 아니라, 상태를 기반으로 계산된 값(파생 상태)을 만들 수 있습니다. 예를 들어, 장바구니 총액을 계산하거나, 특정 조건에 맞는 아이템의 개수만 필터링하여 보여주는 등의 로직을
readOnly콜백 내에서 처리할 수 있습니다. - 성능 최적화: 필요한 데이터만 정확히 구독함으로써, 상태의 다른 부분이 변경될 때 발생하는 불필요한 리렌더링을 방지합니다. 이는 특히 복잡한 상태 구조나 빈번한 상태 업데이트가 있는 애플리케이션에서 성능 최적화에 크게 기여합니다.
결과적으로 useCart.readOnly는 상태의 특정 부분이나 파생된 값에만 의존하는 컴포넌트를 만들 때 매우 유용하며, 애플리케이션의 반응성과 성능을 향상시키는 데 도움을 줍니다.
장바구니 페이지 만들기#
장바구니 페이지는 사용자가 선택한 상품들의 목록을 보여주고, 각 상품을 제거하거나 전체 장바구니를 비우는 기능을 제공합니다. 또한, 장바구니에 담긴 모든 상품의 총액을 계산하여 표시합니다. 이 페이지는 주로 Cart 컴포넌트로 구성되며, 각 장바구니 아이템은 ProductCard (또는 별도의 CartItemCard)를 통해 렌더링될 수 있습니다.
Cart 컴포넌트에서는 useCart 훅에 selector 함수를 전달하여 필요한 데이터만 효율적으로 가져옵니다. 이 selector 함수는 현재 장바구니 상태(store)를 인자로 받아, 장바구니 아이템 목록(items)과 전체 아이템의 가격 합계(priceTotal)를 포함하는 객체를 반환합니다. 이렇게 하면 Cart 컴포넌트는 장바구니 아이템 또는 총액이 변경될 때만 리렌더링됩니다.
items: 현재 장바구니에 담긴 상품 객체들의 배열입니다.priceTotal:items배열의 각product.price를 합산한 총액입니다.
미들웨어 합성으로 장바구니 백업 및 동작 확인#
caro-kann의 강력한 기능 중 하나는 미들웨어를 손쉽게 합성하여 스토어의 기능을 확장할 수 있다는 점입니다. 여기서는 장바구니 상태를 브라우저에 유지하고, 상태 변경을 쉽게 추적할 수 있도록 persist와 logger 미들웨어를 적용해 보겠습니다.
1. persist 미들웨어로 장바구니 상태 유지하기#
기본적으로 웹 애플리케이션의 상태는 페이지를 새로고침하거나 브라우저를 닫으면 초기화됩니다. 사용자가 장바구니에 담은 상품들이 사라지지 않도록 하려면 상태를 어딘가에 저장해야 합니다. persist 미들웨어는 이러한 상태 영속성 관리를 매우 간단하게 만들어줍니다.
persist 미들웨어는 상태가 변경될 때마다 지정된 스토리지(예: localStorage)에 자동으로 데이터를 저장하고, 애플리케이션이 다시 로드될 때 저장된 데이터를 불러와 상태를 복원합니다. 다음은 persist 미들웨어를 useCart 스토어에 적용하는 방법입니다.
2. logger 미들웨어로 상태 변경 추적하기#
개발 과정에서 상태가 어떻게 변경되는지, 어떤 액션에 의해 변경이 일어났는지 등을 파악하는 것은 디버깅에 매우 중요합니다. logger 미들웨어는 상태 변경과 관련된 정보를 콘솔에 출력하여 이러한 과정을 쉽게 추적할 수 있도록 도와줍니다.
logger 미들웨어는 persist 미들웨어와 함께 합성하여 사용할 수 있습니다. 미들웨어는 함수를 감싸는 형태로 적용되므로, 안쪽부터 바깥쪽 순서로 실행된다고 생각할 수 있습니다. 즉, 상태 변경이 일어나면 logger가 먼저 동작하고, 그 다음 persist가 동작합니다.
이제 장바구니에 상품을 추가하거나 제거할 때마다 브라우저 콘솔에 관련 로그가 출력되어, 상태 변경 과정을 명확하게 확인할 수 있습니다. logger의 옵션을 조정하여 필요한 정보만 선택적으로 볼 수도 있습니다.
merge로 여러 스토어 합치기#
애플리케이션의 규모가 커지면, 관련된 상태들을 별도의 스토어로 분리하여 관리하는 것이 더 효율적일 수 있습니다. 예를 들어, 장바구니 상태는 useCart 스토어에서, 사용자 프로필 정보는 useProfile 스토어에서 각각 관리할 수 있습니다.
caro-kann은 이렇게 분리된 여러 스토어를 하나의 통합된 훅으로 편리하게 사용하고 관리할 수 있도록 merge 유틸리티를 제공합니다. merge는 여러 스토어 훅을 입력받아, 각 스토어의 상태와 세터(setter) 함수에 접근할 수 있는 새로운 훅을 반환합니다.
먼저, 개별 스토어를 정의합니다. 여기서는 장바구니(useCart)와 사용자 프로필(useProfile) 스토어를 예시로 사용합니다. User 타입도 정의해야 합니다.
이제 merge 유틸리티를 사용하여 useCart와 useProfile 스토어를 하나로 합칩니다. merge 함수에 객체를 전달하며, 각 키는 합쳐진 스토어에서 사용될 이름이 되고 값은 해당 스토어 훅이 됩니다.
이렇게 생성된 useProfileAndCart 훅을 사용하면, 마치 하나의 큰 스토어를 다루는 것처럼 각 스토어의 상태와 세터 함수에 접근할 수 있습니다.
예를 들어, 컴포넌트 내에서 다음과 같이 사용할 수 있습니다.
마무리하며: caro-kann과 함께하는 효율적인 상태 관리#
지금까지 caro-kann 라이브러리를 활용하여 기본적인 장바구니 기능을 구현하는 과정을 함께 살펴보았습니다. 이 튜토리얼을 통해 create 함수로 스토어를 생성하고, useCart 훅을 통해 상태를 읽고 업데이트하는 방법, writeOnly와 readOnly 메서드를 사용한 최적화, 그리고 selector를 활용한 파생 상태 관리 방법을 익혔습니다. 또한, persist와 logger 미들웨어를 합성하여 스토어의 기능을 손쉽게 확장하고, merge 유틸리티로 여러 스토어를 통합적으로 관리하는 방법도 알아보았습니다.
여기서 다룬 내용들은 caro-kann이 제공하는 다양한 기능의 일부에 불과합니다. 실제 애플리케이션 개발에서는 더욱 복잡한 상태 로직, 고급 미들웨어 활용 등 다양한 시나리오와 마주하게 될 것입니다. caro-kann은 이러한 고급 요구사항들을 충족시킬 수 있는 강력하고 유연한 기능들을 제공하며, 개발자가 상태 관리를 더욱 효율적이고 직관적으로 다룰 수 있도록 돕습니다.
더 깊이 있는 학습과 caro-kann의 모든 잠재력을 확인하고 싶다면, 공식 문서를 참조하시는 것을 적극 권장합니다. 공식 문서에서는 이 튜토리얼에서 다루지 못한 다양한 API, 고급 사용 패턴, 그리고 실제 프로덕션 환경에서의 모범 사례들을 자세히 안내하고 있습니다. caro-kann과 함께 더욱 즐겁고 생산적인 상태 관리 경험을 만들어가시길 바랍니다.