요새 ReactJS 와 nextJS 의 공식문서를 읽고 있다. 쓸 줄은 알지만 깊게 공부하고 싶기 때문에 찬찬히 살피는 중이다. 읽으면서 '아 맞다' 했던 개념들이 2가지가 있어서 간략하게 글을 써보고자 한다.
console.log(count);
setCount(count + 1);
console.log(count);
위의 코드에서 마지막 결과는 어떻게 나올까? console 에 0이 찍힌다는 사실!!!
분명히 count 를 증가시켰는데, count 가 여전히 0인 이유는 useState 훅을 사용하면 상태 값의 변경은 비동기적으로 처리되기 때문이다.
좀더 자세하게 말하자면, setCount(count + 1) 을 호출하면 react 는 리렌더링을 예약한다. 이 리렌더링이 완료된 후 새로운 상태값이 적용된다.
useEffect(() => {
console.log(count); // 상태가 업데이트된 후의 count 값 출력
}, [count]);
상태가 업데이트된 후 값을 확인하면 되므로 useEffect 를 사용하면 된다. 상태 값이 변경된 후에 console 을 출력하기 때문에 내가 의도한대로 출력할 수 있다!
객체나 배열을 복사하기 위해 사용하는 ... 은 Immer 같은 라이브러리를 사용해서 반복적인 코드를 줄일 수 있다!
const original = { name: "John", address: { city: "New York" } };
const copy = { ...original };
'...', 즉 전개연산자는 얕은 복사를 수행한다. javascript 에서 얕은 복사는 객체나 배열의 1차원 속성만 복사되고, 중첩된 객체나 배열은 참조가 복사되는 것을 의미한다. 즉, 중첩된 객체의 경우 원본과 복사본이 같은 객체를 가리키게 되어 하나를 수정하면 다른 것도 영향을 받는다.
const original = { name: "John", address: { city: "New York" } };
const copy = { ...original };
copy.name = "Jane"; // 최상위 속성은 독립적임
copy.address.city = "Los Angeles"; // 중첩된 객체는 참조가 복사됨
// 얕은 복사이므로 다음과 같은 결과가 나옴
console.log(original.name); // "John" (변경되지 않음)
console.log(original.address.city); // "Los Angeles" (변경됨!)
다음과 같은 예시가 있다고 해보자.
import { useState } from 'react';
export default function Counter() {
const [score, setScore] = useState(0);
function increment() {
setScore(score + 1);
}
return (
<>
<button onClick={() => increment()}>+1</button>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
<h1>Score: {score}</h1>
</>
)
}
의도한 건 increment 함수가 3번 실행되어 score 가 3으로 보이게끔 하는 것이다. 하지만! score는 1로 보이는 버그가 있다. 왜그럴까??
useState 는 state 를 설정하면 리렌더링을 예약하고, 완료되면 새로운 state 를 적용한다. 그런데 같은 렌더링 사이클에서 여러 번의 업데이트 요청이 있을 떄 setScore 는 가장 최신의 상태를 기준으로 업데이트하지 않고 이전 상테를 기반으로 각각의 상태 요청을 처리한다.
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
state 가 이전 state를 기반으로 연속적으로 업데이트되려면 functional update 를 사용해야한다. 이 방식은 상태 업데이트 함수가 현재 상태 값을 직접 참조하지 않고, 이전 상태 값을 인수로 받아 그 값을 기반으로 새로운 값을 계산한다.
import { useState } from 'react';
export default function Counter() {
const [score, setScore] = useState(0);
function increment() {
/*
* 이렇게 수정하면 setScore는 각 호출에서 최신 상태 값을 참조하게 되어
* increment()를 3번 호출하면 score가 3씩 증가하게 된다.
*/
setScore(prevScore => prevScore + 1);
}
return (
<>
<button onClick={() => increment()}>+1</button>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
<h1>Score: {score}</h1>
</>
);
}