늘상 사용하던 코드에 대해 의문을 갖지 않다가 인강을 들으며 왜 이렇게 사용해야 하는지 이해하고 있습니다. 오늘은 리액트에서 useState를 사용하여 상태 업데이트를 할 때 왜 콜백함수인 (prevUserInput) => { ... } 와 같은 형태로 써야 하는지에 대해 포스팅 하겠습니다.
우선 오늘 배운 코드는 아래와 같습니다.
import { useState } from "react";
export default function UserInput() {
const [userInput, setUserInput] = useState({
initialInvestment: 10000,
annulInvestment: 1200,
expectedReturn: 6,
duration: 10,
});
function handleChange(inputIdentifier, newValue) {
setUserInput((prevUserInput) => {
return {
...prevUserInput,
[inputIdentifier]: newValue,
};
});
}
return (
<section id="user-input">
<div className="input-group">
<p>
<label>Initial Investment</label>
<input
type="number"
required
value={userInput.initialInvestment}
onChange={(event) =>
handleChange("initialInvestment", event.target.value)
}
/>
</p>
<p>
<label>Annual Investment</label>
<input
type="number"
required
value={userInput.annulInvestment}
onChange={(event) =>
handleChange("annulInvestment", event.target.value)
}
/>
</p>
</div>
<div className="input-group">
<p>
<label>Expected Return</label>
<input
type="number"
required
value={userInput.expectedReturn}
onChange={(event) =>
handleChange("expectedReturn", event.target.value)
}
/>
</p>
<p>
<label>Duration</label>
<input
type="number"
required
value={userInput.duration}
onChange={(event) => handleChange("duration", event.target.value)}
/>
</p>
</div>
</section>
);
}
이 중 아래와 같이 onChange시 useState를 업데이트 할 때 왜 아래와 같은 방식(prevUserInput 사용 부분)으로 써야만 하는지에 대해 알아보겠습니다.
function handleChange(inputIdentifier, newValue) {
setUserInput((prevUserInput) => {
return {
...prevUserInput,
[inputIdentifier]: newValue,
};
});
}
결론부터 말하자면, 위에서 (prevUserInput)을 사용하는 이유는 React의 useState에서 상태를 업데이트할 때 이전 상태 값을 안전하게 참조하기 위해서입니다.
prevState를 사용하지 않는 경우
만약 prevState를 사용하지 않고 업데이트 하면 어떻게 될까요?
function handleChange(inputIdentifier, newValue) {
setUserInput({
...userInput,
[inputIdentifier]: newValue,
});
}
위 코드에서 setUserInput은 이전 상태(userInput)를 직접 참조하여 새로운 상태를 설정하고 있습니다. 여기서 문제가 발생할 수 있습니다. 같은 방식으로 상태를 업데이트하면 동시에 여러 상태 업데이트가 발생할 경우 문제가 될 수 있지요. userInput의 최신 상태를 정확하게 참조하지 못할 가능성이 있어 그 결과 일부 상태 업데이트가 반영되지 않거나, 잘못된 상태로 업데이트될 수 있습니다.
예를 들어 사용자가 다음과 같은 값을 동시에 수행한다고 가정해보겠습니다.
handleChange("initialInvestment", 15000)
handleChange("annulInvestment", 2000)
이 경우 setUserInput은 userInput의 최신 상태를 보장하지 못합니다.
- 첫 번째 호출에서 15000으로 initialInvestment가 설정된 이후, 두 번째 호출에서 userInput 상태가 갱신되기 전에 그 이전 상태인 2000으로 annualInvestment가 설정될 수 있습니다.
- 결과적으로 최종 userInput 상태는 initialInvestment가 초기 값인 10000으로 남아있을 수 있고, annualInvestment만 업데이트될 가능성이 있습니다.
즉 setUserInput 호출에서의 userInput 상태와 두 번째 호출에서의 상태가 충돌할 수 있어서 의도한 대로 결과가 반영되지 못하는 것입니다. 최신 상태를 반영하지 못하는 것이죠.
대안으로 (prevUserInput) => { ... } 사용
위와 같은 문제를 해결하기 위해서 리액트에서 권장하는 방식은 setUserInput 함수에 콜백함수 '(prevUserInput) => { ... }' 을 사용하는 것입니다.
setUserInput 함수에 콜백 함수 (prevUserInput) => { ... }을 전달하는 방식은 항상 최신의 이전 상태 값을 보장합니다. 이 콜백 함수의 인자로 전달되는 prevUserInput은 상태 업데이트가 발생할 때마다 React가 자동으로 현재의 최신 상태 값을 전달해주는 역할을 합니다. 따라서, 여러 상태 업데이트가 동시에 발생해도 안전하게 상태를 업데이트할 수 있습니다.
다시 올바른 형태로 쓰인 코드를 확인해보겠습니다.
function handleChange(inputIdentifier, newValue) {
setUserInput((prevUserInput) => {
// prevUserInput은 항상 최신 상태를 반영함
return {
...prevUserInput,
[inputIdentifier]: newValue,
};
});
}
위 코드에서 prevUserInput을 사용하면 setUserInput이 호출될 때마다 최신 상태에 기반해 업데이트가 이루어지기 때문에 예기치 않은 상태 누락이나 잘못된 상태 업데이트를 방지할 수 있습니다.
React에서는 상태가 비동기적으로 업데이트 되기 때문에 위와 같은 'prevUserInput'을 사용하면 상태 업데이트가 연속적으로 발생할 때도 안전하게 이전 상태를 반영할 수 있게 되는 것입니다.
'인강 & 책 스터디 노트 > Udemy 리액트 완벽 가이드 2024' 카테고리의 다른 글
리액트에서 불변성을 유지해야 하는 이유 -기본형과 참조형이란 (0) | 2024.08.21 |
---|---|
[React]같은 '...'이지만 쓰임이 다른 스프레드 연산자와 Rest 프로퍼티 (0) | 2024.08.13 |
댓글