보다 복잡한 리액트 앱을 만들 때에는
컨텍스트가 매우 중요한 기능이 될 수 있는데
그것은 컨텍스트가 다수의 컴포넌트간의 상태 공유를 돕기 때문입니다
그렇다면 상태 관리는 어떨까요?
여기 있는 이 컨텍스트에서
우리는 장바구니의 상태를 관리합니다
그리고 여러분도 보이시겠지만
이러한 상태 업데이트용 함수는 다소 복잡합니다
약간의 로직이 더 포함되어 있기 때문인데
적어도 코드 두어줄 정도는 더 많습니다
그리고 대다수의 경우가 이렇습니다
그렇지만 이것이 의미하는 바는
여기에 있는 CartContextProvider 컴포넌트 함수가
약간은 더 읽기 어려워졌다는 것을 뜻하기도 합니다
그보다 더 중요한 것은
보시다시피 제가 항상 사용하는 함수 양식은
이러한 상태 업데이트 함수와 같습니다
저는 언제나 이렇게 함수를 전달하는데
그 이유는 간단히 말해
보다 복잡한 상태 객체, 배열 등을 관리할 때면
거의 항상이라도 해도 좋을 만큼
이전 상태 snapshot(스냅샷)을 기반으로
상태를 업데이트해야 할 것이기 때문입니다
이것은 여러분이 언제나 사용하게 될 패턴인데
어찌 보면 매번 같은 패턴을 사용해야 하기 때문에
좀 성가시기도 합니다
그렇기에 리액트에서는
useState와 같은 코드를 사용해 상태 관리를 하는 대신
리액트가 제공하는 또다른 상태 관리용 훅을 사용할 수 있는데
이것을 useReducer라고 합니다
reducer(리듀서)는 무엇일까요?
리액트 앱과 자바스크립트 프로그래밍에서의 리듀서란
대개 하나 또는 그 이상의 복잡한 값을
더 단순한 형태로 만드는 함수입니다
예를 들자면 이러한 숫자의 집합체를
모두 더해주어 하나의 숫자로 만드는 것과 같은 것입니다
이 리듀서라고 정의하는 기능이
이것과 같은 덧셈을 대신 해 주는 것입니다
그리고 사실 제가 여러분을 위해 준비한 이 데모 프로젝트에서도
이미 사용되고 있는 리듀서 기능이 있는데
이것은 Cart.jsx(장바구니 파일)에서 보실 수 있습니다
여기에서 저는 내장된 리듀서를 사용하고 있는데
자바스크립트의 어떠한 배열에서도 사용이 가능합니다
리액트와는 완전히 별개이지만
여기 있는 이 useReducer 훅이 매우 중요합니다
이것은 리액트 이외의 프로젝트에서도 동작하는 방식입니다
저는 이 방법을 사용해 이 items 배열의 모든 items에 실행할
저의 리듀서 기능을 정의하겠습니다
여기에서는 단순히 시작하는 값을 기반으로
장바구니의 총액을 업데이트 하는데
장바구니에 들어있는 물건의 수량을 각 물건의 가격에 곱해
이를 더하는 것입니다
이 함수에서 제가 하고 있는 것은 바로 그것입니다
그리고 items의 배열을 하나의 숫자
이 경우에는 이 모든 물건들의 총액이겠습니다만
하나로 줄이는 이 자체가 바로 리듀서 기능인 것입니다
이 useReducer 훅이라는 것은
상태 관리의 목적을 가지고 하나 또는 그 이상의 값을
보다 단순하게 하나의 값으로 줄이는 것입니다
상태 관리에 useReducer를 어떻게 사용하면 좋을까요?
리액트 컴포넌트에서는
이 경우에는 CartContextProvider 컴포넌트인데
다른 리액트 훅 기능을 실행하는 것과 같은 방법으로
useReducer 훅을 실행하면 됩니다
그러면 useReducer 훅은 정확히 두 개의 요소를 가진
배열을 만들어낼 것인데
useState가 항상 두 개의 요소를 가진 배열을 만드는 것과
동일하다고 보시면 됩니다
useState와 마찬가지로 useReducer의
첫 번째 요소는 useReducer로 관리되는
상태입니다
물론 이것에 shoppingCartState나
그와 비슷한 이름을 붙이는 것도 괜찮습니다
그러나 useReducer로부터 받게 되는
이 배열의 두 번째 요소는
useState에서와 같은
상태 업데이트가 아닌
dispatch 함수입니다
이것으로는 action(액션)을 보낼 수 있는데
보내진 액션은 추후
리듀서 기능에 의해 사용됩니다
우선 이것을 shoppingCartDispatch 라고 이름을 붙여서
이 상태에 속한 것을 명확히 해 두겠습니다
물론 타자를 덜 치기 위해 그냥 dispatch라고만 하셔도 됩니다
제 경우는 shoppingCartDispatch를 사용하겠습니다
이제 우리에겐 이렇게 상태와 디스패치 기능이 생겼는데
말씀드렸던 바와 같이 우리에겐
값을 보내주는 것을 시작으로 실행되어
새로운 상태를 생성해줄
리듀서 기능이 필요합니다
그것이 바로 이 훅의 목적이며
곧 추가할 리듀서 기능이기도 합니다
그러므로 이 컴포넌트 함수의 밖에
새로운 함수를 하나 더하고
이를 shoppingCartReducer라고 하겠습니다
이렇게 이 컴포넌트 함수의 밖에 만드는 이유는
앞서 만든 함수가 실행될 때마다 이 새로운 함수가
재생성되지 않도록 하기 위해서이기도 합니다만
앞서 만든 함수에서 정의 또는 업데이트되는 그 어떤 값에도
직접적인 액세스를 필요로 하지 않기 때문이기도 합니다
즉 속성 등에 접근할 필요가 없다는 뜻입니다
그렇기 때문에 이 컴포넌트 함수 밖에 이렇게 정의하도록 하겠습니다
이 리듀서 함수는 이제 두 개의 매개변수를 받게 되는데
각각의 인수는 상태와 액션입니다
이 두개의 인수를 받는 이유는
액션이 디스패치를 통해 보내진 후에
리액트가 리듀서 함수를 호출할 것이기 때문입니다
조금 뒤에 여러분도 보시게 될 겁니다
여러분이 디스패치 기능을 이용해
보내는 액션은
사실 이 두 번째 파라미터에서
여러분이 받게 되는 액션과 동일합니다
액션이 사용되는 예시는 곧 보시게 될 겁니다
반면 이쪽에 생기는 상태는
useReducer로 관리되는 상태의
보장된 최신 상태 스냅샷입니다
상태 업데이트를 위해 이 함수 형태를 사용하여
최신으로 보장된
상태 스냅샷이 생길 때
이 리듀서 함수를 호출하는 리액트는
첫 번째 인수로 여러분이
가장 최근의 상태를 받도록 할 것입니다
이제 여러분은 shoppingCartReducer 함수 안에
업데이트된 상태를 반환해야 합니다
아직은 상태가 바뀌지 않았습니다만 곧 업데이트가 될 테니
일단은 바뀌지 않은 상태를
이 상태 그대로 반환하겠습니다
아무튼 이렇게 우리에겐 기본적인
현재로서는 크게 유용하지는 않은 리듀서 기능이 생겼습니다
다음 단계로는 이 리듀서 기능을
useReducer 훅에 연결해 보도록 하겠습니다
그렇게 하기 위해서는 리듀서 기능에 포인터(pointer)를 전달해서
useReducer의 첫 인수가 되도록 해야 합니다
이 리듀서 기능이 이제 리액트에 등록되었기 때문에
여러분이 디스패치를 사용할 때면 언제든 실행될 것입니다
때로 상태 위한 초기값 설정을 가능하게 만들어줄
두 번째 값을 useReducer에 전달하고자 할 때가 있을 수도 있는데
상태를 한 번도 업데이트 되지 않은 상황에서
사용할 수 있습니다
우리가 useState를 호출할 때
여기에서 하는 것과 동일한 것입니다
제가 여기에 설정하는 이 초기 상태는
그대로 복사한 뒤 이 리듀서를 위한
초기 상태로 설정이 가능합니다
그러면 useReducer의 arguments로 초기 상태와
리듀서 기능이 갖춰지는 것입니다
이와 함께, 이 초기 상태는 여기에서 받아서
상태로서 반환됩니다
이제 우리는 useState를 사용하며 얻은 shoppingCart 상태 대신
useReducer가 반환한 첫 번째 요소인
shoppingCartState 값을 여기에 사용할 수 있습니다
그리고 이 shoppingCartState를 여기 아래에 있는
해당 상태의 items에 접근을 위해 생성한 컨텍스트 값에
사용할 수 있는 것입니다
현재 시점에서 이것이 의미하는 바는
제 리듀서의 초기 상태는 비어있는 items 배열로 설정되어 있기 때문에
여기도 언제나 그렇게 적용되어 있을 것이고
변화를 줄 그 어떤 로직도 여기에 없다는 것입니다
그렇지만 일단은 이것으로 충분합니다
이제 파일을 모두 저장하고
앱을 새로고침하면
아무런 오류가 나타나지 않을 것입니다
그리고 장바구니에는 아무 것도 없습니다
잘 작동하고 있습니다
이제 상태를 변경하면
장바구니는 더 이상 업데이트를 진행하지 않게 되는데
이것은 새롭게 추가된 리듀서를 기반으로 한 상태로부터
해당 값을 받게 되었기 때문입니다
물론 지금은 그 값을 업데이트 해줄
그 어떤 로직도 넣어두지 않았지만
다음엔 그 부분도 바뀔 것입니다