useState
라는 것은 state를 관리하는 React Hook중 하나입니다. React 라이브러리의 하나의 기능입니다.
모든 후크는
use
라는 단어로 시작함으로서 React Hook임을 알 수 있습니다.
useState
의 작동방식에 대해서 알아봅시다.
import "./ExpenseItem.css";
import React, { useState } from "react";
export default function ExpenseItem(props) {
const [title, setTitle] = useState(props.title);
const clickHandler = () => {
setTitle("Update");
console.log(title);
};
return (
<div className="expense-item" onClick={() => clickHandler()}>
<div>{props.date.toISOString().slice(0, 10)}</div>
<div className="expense-item__description">
<h2>{title}</h2>
<div className="expense-item__price">${props.amount}</div>å
</div>
</div>
);
}
useState는 react 라이브러리에서 가져와야합니다. useState라는 함수는 인수로 state 변수의 초기값을 받고 배열을 반환합니다. 이때 [title, setTitle] 이라는 구문은 자바스크립트의 배열 구조 분해라는 형태로 useState에서 현재 상태에 대한 스냅샷을 가져옵니다. 즉 ExpenseItem 이라는 컴포넌트가 렌더링될때마다 현재상태에 대한 스냅샷을 title, setTitle 에 넣어주는 형태입니다.
반환하는 배열은 첫번째로 state변수, state변수를 수정하는 함수를 반환하며
state변수는 state변수를 수정하는 함수로만 업데이트가 가능합니다.
처음 렌더링후 사용자가 컴포넌트를 클릭해서 setTitle()
함수가 실행이되면 인수값으로 state변수를 수정할 것을 예약
합니다. 그리고 컴포넌트의 재평가를 유발시킵니다. 따라서 이후 시작되는 console.log(title)은 아직 state변수가 바뀌지 않았으므로 이전 값이 출력됩니다. 왜냐하면 title은 현재 useState에서 가져온 스냅샷을 받아온 상태인데 이 스냅샷은 setTitle 함수가 실행되기 전이기 때문입니다. 따라서 실제로 변경된 title 값이 반영되려면 ExpenseItem 컴포넌트가 리렌더링되고 새로운 스냅샷을 title에 저장을 하고 나서 컴포넌트가 렌더링될때 반영됩니다.
이후 setTitle은 컴포넌트의 재평가를 유발합니다. 컴포넌트(함수)가 재실행되면서 state변수가 들어가있는 JSX 요소만을 리 렌더링 합니다. 리 렌더링과정에서 컴포넌트 자체가 재실행되면서 state변수가 변경된 부분만 비교를 해서 알아내고 그부분만 리 렌더링 합니다.
즉, state변수가 변경되면 해당 state변수가 등록된 곳만 리렌더링을 합니다.
const [value, setValue] = useState(1);
setValue(value + 1);
setValue(value + 1);
setValue(value + 1);
// 2 why not 4?
위 코드는 새로운 상태값을 반영하고 그즉시 value가 변경되지 않습니다. 왜냐하면 value는 closure 범위에서 관리되고 있기때문에 setValue 함수자체가 react에 대해서 value값에 대해 리렌더링을 예약하고 리렌더링 된후에 값을 사용하기 때문입니다. setValue는 값변경을 batch 라는 개념을 사용합니다.
이렇게 비동기형식으로 값이 변경되는데 useState는 값을 변경할때에 이전값을 참고합니다.
위 코드에서 각각의 value는 업데이트 되지 않은 상태에서 이전값을 참고하므로 setValue 3개 모두 1 + 1이라는 계산을 합니다.
이를 위해 setValue는 함수형 업데이트를 제공합니다.
setValue의 인수로서 함수를 받을수 있는데 이때 이함수에서는 이전의 값을 인수로 받아들입니다. 따라서 함수 자체를 넘길때도 비동기 처리되므로 해당 함수가 즉시 실행되지는 않더라도 큐에 함수 자체가 쌓이게 되고 쌓인 함수가 처리될 때 preState 값은 계속해서 이전값을 참고하므로 순서대로 계산됩니다.
const [value, setValue] = useState(1);
setValue((preState) => preState + 1);
setValue((preState) => preState + 1);
setValue((preState) => preState + 1);
// 4
객체같은 경우는 다음과 같이 업데이트가 가능합니다.
const [value, setValue] = useState(
{name :"doodream" , email: "doodream17@naver.com"}
);
setValue((preState) => {return {...preState, name : "노두현"}});
❗️ 착각하지 말것은 setState()는 바로 다음코드에는 적용되지 않는다는 것입니다.
const [value, setValue] = useState(1); setValue((preState) => preState + 1); console.log(value) // 1;