티스토리 뷰
다음 렌더링 전에 동일한 상태 변수를 여러 번 업데이트해야 하는 경우가 있을 수 있다.
이런 경우 다음 상태 값을 전달하는 대신 다음 상태 값을 기반으로 새로운 상태 값을 계산하도록 할 수 있다.
이는 React에게 상태 값을 단순히 교체하는 대신 “이전 상태”를 기반으로 무언가를 수행하도록 지시하는 방법이다.
예를 들어 장바구니의 담겨있는 동일 상품을 하나가 아닌 여러 개를 사고 싶어 + 버튼을 통해 이전 수량에서 1씩 추가를 해야 한다고 가정을 해보자.
상품 데이터가 있는 json 파일을 먼저 만들어준다.
[product.json]
{
"id": "nf-uni-padding",
"imageSrc": "NFPadding.jpg",
"imageAlt": "검정색 노스페이스 남여공용 패딩",
"name": "노스페이스 패딩",
"birthday": "2022-1-21"
}
해당 노스페이스 패딩 제품이 장바구니에 담겨있다고 가정하고 수량을 추가해보는 부분만 구현해보자.
import { useState } from 'react';
import { getStaticImage } from '../utils'; // 이미지 경로 설정 함수
import productData from '../data/product.json';
ㄴ 상태 관리를 위해 useState 를 불러온다.
ㄴ getStaticImage 유틸 함수는 인수에 파일명을 넣어주면 images 폴더에 저장되어 있는 이미지 중 일치하는 이미지를 가져오는 함수이다.
ㄴ 위에서 만든 상품 정보가 담겨있는 json 파일을 productData 이름으로 가져와서 사용한다.
const [product, setProduct] = useState(0);
ㄴ 초깃값은 0으로 설정해주고 state 변수 이름은 product 로 설정한다.
(상품의 개수가 담겨있는 거라 네이밍이 적절치 않지만 예제로 만든거니 대충 넘어가자)
ㄴ 상품 개수를 업데이트 해주는 setter 함수의 이름은 setProduct 로 설정한다.
상태 변수를 새로운 값으로 단순히 교체하고자 하는 것이기 때문에 아래와 같이 코드를 작성해준다.
const handleIncreaseProduct = () => {
setProduct(product + 1);
};
ㄴ 이 핸들러는 상품 수량을 증가시켜주는 핸들러이다.
귀찮은 사용자가 있다는 가정을 하고 학습에 대한 이해를 위해 2개씩 늘리는 코드를 작성하였다고 가정해보자.
const handleDoubleIncreaseProduct = () => {
setProduct(product + 1); // 0 + 1
setProduct(product + 1); // 0 + 1
};
ㄴ 이 핸들러는 setProduct(product + 1); 를 두 번 호출하고 있다.
ㄴ 만약 해당 함수를 실행할 경우 product 의 값이 2가 아닌 1로 표시된다.
이 문제는 새로운 값 업데이트의 주의사항에서 다룬 내용과 비슷한데 setter 함수를 호출해도 이미 실행 중인 코드의 상태 변수가 업데이트 되지 않기 때문이다.
따라서 각 호출은 setProduct(1) 이 되는 것이다.
이 문제를 해결하려면 다음 상태 대신 setProduct 에 업데이트 함수를 전달하면 된다.
const handleDoubleIncreaseProduct = () => {
setProduct((product) => product + 1); // setProduct(0 => 1)
setProduct((product) => product + 1); // setProduct(1 => 2)
};
ㄴ 해당 코드는 이전 상태 를 기반으로 업데이트를 시도한다. 다시 말해 여러 번 상태를 업데이트 하는 것이다.
ㄴ (product) product + 1 이 함수는 업데이트 함수이다.
이 안에서는 보류 상태(pending state)를 가져와서 다음 상태를 계산한다.
React는 업데이트 함수들을 대기열(큐)에 넣는다.
그리고 나서 다음 렌더링 중에 동일한 순서로 호출한다.
- 처음 (product) product + 1 은 보류 상태로 0을 받고 다음 상태로 1을 반환한다.
- 그 다음 (product) product + 1 은 보류 상태로 1을 받고 다음 상태로 2를 반환한다.
이 과정으로 대기 중인 다른 업데이트가 없어 React는 결국 현재 상태(current state)로 2를 저장하는 것이다.
return (
<>
<figcaption>
<img
height={250}
width={250}
src={getStaticImage(productData.imageSrc)}
alt={productData.imageAlt}
/>
<span>수량 {product}</span>
<button
type="button"
aria-label="상품 수량 1 증가"
onClick={handleIncreaseProduct}
>
1개씩 더 담기 +
</button>
<button
type="button"
aria-label="상품 수량 2 증가"
onClick={handleDoubleIncreaseProduct}
>
2개씩 더 담기 +
</button>
</figcaption>
</>
);
마크업의 구조는 위와 같다.
span 요소에는 장바구니에 담긴 상품의 수량이 표시된다.
첫 번째 버튼은 onClick 이벤트를 걸어 handleIncreaseProduct 핸들러를 참조한다.
(상품이 1개씩 증가)
두 번째 버튼에도 마찬가지도 onClick 이벤트를 걸어 handleDoubleIncreaseProduct 핸들러를 참조한다.
(상품이 2개씩 증가)
[렌더링 결과]
[JSX 파일 전체 코드]
import { useState } from 'react';
import { getStaticImage } from '../utils'; // 이미지 경로 설정 함수
import productData from '../data/product.json';
function Cart() {
const [product, setProduct] = useState(0);
const handleIncreaseProduct = () => {
setProduct(product + 1);
};
const handleDoubleIncreaseProduct = () => {
setProduct((product) => product + 1); // setProduct(0 => 1)
setProduct((product) => product + 1); // setProduct(1 => 2)
};
return (
<>
<figcaption>
<img
height={250}
width={250}
src={getStaticImage(productData.imageSrc)}
alt={productData.imageAlt}
/>
<span>수량 {product}</span>
<button
type="button"
aria-label="상품 수량 1 증가"
onClick={handleIncreaseProduct}
>
1개씩 더 담기 +
</button>
<button
type="button"
aria-label="상품 수량 2 증가"
onClick={handleDoubleIncreaseProduct}
>
2개씩 더 담기 +
</button>
</figcaption>
</>
);
}
export default Cart;
참고: https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state
'Development' 카테고리의 다른 글
"remote: Repository not found." 에러 해결 (0) | 2024.04.04 |
---|---|
UX 향상을 위한 Skeleton 라이브러리 사용하기 (React & Tailwind CSS) (0) | 2024.03.12 |
React의 작동 흐름 (공식 문서 해설) (0) | 2024.01.30 |
웹 컴포넌트(Web Component) API - 슬롯(Slot) (0) | 2024.01.25 |
프레그먼트(Fragments) (1) | 2024.01.25 |