컴포넌트에서 state 변수를 추가할 수 있는 React Hook 이다.
useState(initialState)
const [state, setState] = useState(initialState);
initialState
: 초기 상태값을 지정한다.useState
는 두개의 값을 가진 배열 반환
initialState
을 통과한 첫번째 상태setState()
setState(nextState)
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ...
nextState
: 변경할 상태값이다.setState()
은 다음 렌더링에서 사용될 상태 변수를 업데이트한다. 고로 상태 변경후 바로 state 를 사용하면 아직 다음 렌더링이 실행되지 않았기 때문에 변경전 state 값을 가지고 있는다.setState()
함수를 호출해 화면을 업데이트한다. 각 이벤트 핸들러마다 상태를 업데이트할 경우 렌더링이 자주 일어나기 때문이다.useState
로 초기 상태값과 상태 변경 함수를 얻을 수 있다.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
setState
으로 상태를 변경하면 새 값으로 컴포넌트를 다시 렌더링하고 UI를 업데이트한다.
function handleClick() {
setName('Robin');
}
여러번 이전 상태를 기반으로 업데이트를 실행할 때 실행중인 코드의 상태 변수는 즉시 업데이트되지 않기 때문에 아래 코드의 각 호출에서 age는 43으로 업데이트된다.
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
이때 업데이트 함수를 전달함으로서 해결할 수 있다.
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
원리는 다음과 같다. 콜백함수 내의 이전 상태값을 가져와서 다음 상태값을 계산한다. 그리고 변경한 상태값을 임시로 유지하고 있다가(pending) 이후 로직에서 또 업데이트 함수가 있는지 확인하고 있다면 임시 상태값을 기반으로 업데이트 함수를 실행한다. 마지막 업데이트 함수까지 계산했으면 마지막으로 업데이트된 상태값을 최종 상태값으로 저장한다.
객체와 배열의 상태를 업데이트할때는 기존 객체를 변경하는 대신 교체해야한다.
useState
는 이전 state 값과 현재 state 값이 변경되었는지 확인하고 렌더링 여부를 결정한다. 그러나 객체 내부 값을 변경하는 것은 객체의 참조값을 변경하지 않으므로 값이 변경되었다는 사실을 알리기위해서는 새로운 객체의 참조값으로 교체해야 한다.
// 🚩 다음과 같이 상태를 변경하면 안된다
form.firstName = 'Taylor';
// ✅ 새로운 객체로 교체
setForm({
...form,
firstName: 'Taylor'
});
useState
에서 초기값을 설정은 첫 컴포넌트 렌더링에서만 실행된다. 그러나 초기값 설정에 함수 실행을 전달하면 매 렌더링 마다 함수가 실행된다.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
이 문제를 해결하기 위해 다음과 같이 초기화 함수의 실행 대신 함수의 참조값을 전달해 첫 렌더링에만 함수가 실행되게 할 수 있다.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
컴포넌트로 전달하는 key값을 변경하면 컴포넌트가 초기화된다.
import { useState } from 'react';
export default function App() {
const [version, setVersion] = useState(0);
function handleReset() {
setVersion(version + 1);
}
return (
<>
<button onClick={handleReset}>Reset</button>
<Form key={version} />
</>
);
}
function Form() {
const [name, setName] = useState('Taylor');
return (
<>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<p>Hello, {name}.</p>
</>
);
}
setState
는 즉시 값이 변경되지 않는다는 점을 이용해서 이전 렌더링 정보를 기반으로 현재 렌더링을 구성할 수 있다. 이 방법은 복잡하지만 useEffect
를 사용하는 것보다 나은 방법이라고 한다.
import { useState } from 'react';
export default function CountLabel({ count }) {
const [prevCount, setPrevCount] = useState(count);
const [trend, setTrend] = useState(null);
console.log(count, prevCount)
if (prevCount !== count) {
setPrevCount(count);
setTrend(count > prevCount ? 'increasing' : 'decreasing');
}
return (
<>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</>
);
}
위의 예제는 props
가 이전값과 비교해서 증가했는지 감소했는지를 판별하고싶다.
먼저 CountLabel 은 count 를 props 로 전달 받는다. 이때 count 값이 변경되면 이전 count 값과 다르게되어 조건문이 실행된다.
조건문 내에서 setPrevCount 를 통해 이전 count 값을 업데이트하지만 setState 특성상 현재 코드 흐름에 바로 값이 업데이트되지 않는다.
따라서 setTrend 에서 prevCount 는 아직 이전 렌더링의 count 정보를 가지고 있고 따라서 이전 count 값과 현재 count 값의 비교를 통해 값의 증감을 판별할 수 있다.