리액트가 상태 변경을 감지하기 위해 만족되어야 하는 조건
const [count, setCount] = useState(0);
1) 상태 변경 함수가 호출되어야 한다.
count = 2;
이런 식의 재할당은 X
2) 상태 변경 함수에 전달된 인자와 기존의 상태가 달라야 한다.
setCount(0) // => X : 0 === 0 true
setCount(1) // => O : 0 === 1 false
상태가 참조형 데이터라면? => 참조형 데이터는 자신의 값으로 주소를 담고 있음.
주소가 달라져야 상태 변경을 인지한다.
아래는 코드스테이츠에서 제공 받은 'CMarket' 이라는 싱글 페이지 어플리케이션이다.
path="/"
상품리스트 페이지
path="/shoppingcart"
장바구니 페이지
더미 데이터
export const initialState =
{
"items": [
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900
},
{
"id": 2,
"name": "2020년 달력",
"img": "../images/2020.jpg",
"price": 12000
},
{
"id": 3,
"name": "개구리 안대",
"img": "../images/frog.jpg",
"price": 2900
},
{
"id": 4,
"name": "뜯어온 보도블럭",
"img": "../images/block.jpg",
"price": 4900
},
{
"id": 5,
"name": "칼라 립스틱",
"img": "../images/lip.jpg",
"price": 2900
},
{
"id": 6,
"name": "잉어 슈즈",
"img": "../images/fish.jpg",
"price": 3900
},
{
"id": 7,
"name": "웰컴 매트",
"img": "../images/welcome.jpg",
"price": 6900
},
{
"id": 8,
"name": "강시 모자",
"img": "../images/hat.jpg",
"price": 9900
}
],
"cartItems": [
{
"itemId": 1,
"quantity": 1
},
{
"itemId": 5,
"quantity": 7
},
{
"itemId": 2,
"quantity": 3
}
]
}
'상품 리스트' 페이지에서 장바구니 담기 버튼을 클릭하면 호출되는 함수
const handleClick = (e, id) => {
for(const el of cartItems) { // cartItems는 App.js에서 props로 전송 받음
if(el.itemId === id) {
el.quantity++;
// const copy = [...cartItems];
// setCartItems(copy);
console.log(cartItems);
return;
}
}
const newCartItem = {
"itemId": id,
"quantity": 1
}
const copy = [...cartItems];
copy.push(newCartItem);
setCartItems(copy);
}
위 함수 내부에는 두 가지 상황이 있다.
1. 장바구니에 이미 있는 item을 또 담을 경우
2. 장바구니에 없는 item을 담을 경우
1번의 경우 기존 cartItems 배열에서 입력받은 id와 같은 itemId를 가지고 있는 객체 요소를 찾아서 그 객체의 quantitiy를 1 증가시켜줘야 한다. 2번의 경우 새로운 객체를 만들어서 배열에 push해주면 된다.
그런데 내가 작성한 코드(1번)에는 몇가지 의문점이 있다.
function App() {
const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);
...
}
1. el
은 App에서부터 전송 받아온 cartItems
라는 state의 요소인데, 그걸 직접 수정하는 것이 옳은 방법인가?
2. setCartItems로 수정하지 않아도 페이지가 리렌더링되는 현상은 왜 그런 것인가?
1 A) 옳지 않다. 다른 로직이 중간에 끼어들 경우 오류가 발생할 수 있다. 지금은 코드가 간단해서 괜찮은 것 뿐..
2 A) 리렌더링되는 것이 아니다. cartItems만 변경된 것이다. setCartItems가 호출되지 않았기 때문에 리액트는 상태 변경을 감지할 수 없다. useEffect로 콘솔을 찍어보자.
useEffect(()=>{
console.log('re-rendered');
}, [cartItems])
배열이 수정된 것에 대한 콘솔은 찍히지만 're-rendered'가 찍히지 않았다. 리렌더링되지 않은 것임.
cartItems를 직접 건들지 않는 식으로 코드를 수정해보았다.
수정한 코드는 아래와 같다.
const handleClick = (e, id) => {
const index = cartItems.findIndex((el) => el.itemId === id); // 있는지 없는지 체크
if (index === -1) {
const newItem = {
"itemId": id, // itemId: id,
"quantity": 1, // quantity: 1 로 써도 된다. 객체의 키값은 무조건 문자열임
}
const copy = [...cartItems];
copy.push(newItem);
setCartItems(copy);
} else {
const copy = [...cartItems]; // 기존의 배열을 건드리지 않도록 새로 만들어서
copy[index].quantity++; // quantity 증가시켜준다.
setCartItems(copy);
}
console.log(cartItems);
}
+ input type 'number'
input type을 'number'로 하면 위와 같이 1씩 증감시킬 수 있는 버튼이 기본적으로 생긴다.
처음에는 저걸 몰랐어서 저 버튼 하나 하나에 increase, decrease 기능을 주려고 했는데 그게 아니었다.
<input
type="number"
min={1}
className="cart-item-quantity"
value={quantity}
onChange={(e) => {
handleQuantityChange(Number(e.target.value), item.id)
}}>
</input>
onChange 이벤트로 handleQuantityChange 함수를 호출하고, 인자로는 e.target.value(숫자)와 item의 id를 넘겨준다.
const handleQuantityChange = (quantity, itemId) => {
setCartItems(cartItems.map(el => {
if (el.itemId === itemId) {
return {
"itemId": itemId,
"quantity": quantity
};
} else return el;
}));
}
피드백
findIndex
를 쓰는 게 뭔가 손에 잘 안 붙는다. map, filter
사용하는 건 익숙한데 forEach, findIndex 이런 건 아직 좀 더 많이 써봐야할 것 같다. 그때 그때 필요한 메서드는 분명히 존재한다. filter나 for + if 로 다 퉁치려고 하지 말자.
가급적이면 state로 정의된 요소는 직접 수정하지 않고 우회해서 수정하는 방식을 찾아내자.
'부트캠프 > TIL' 카테고리의 다른 글
Day50. CMarket Redux 리덕스야 덤벼 (0) | 2023.02.26 |
---|---|
Day 50. Redux야 덤벼라 (0) | 2023.02.24 |
Day 48. 리액트 컴포넌트 만들기(with styled-components) (0) | 2023.02.22 |
Day 43. [사용자 친화 웹]UI/UX( in 프론트엔드 ) (0) | 2023.02.15 |
Section 2 회고 (0) | 2023.02.10 |