useState는 리액트의 상태를 정의하는 가장 기본적인 hook으로서 상태 유지 값과 그 값을 갱신하는 함수를 반환한다.
const [state, setState] = useState(initialState);
setState(nextState);
useState의 기본적인 사용방법이다. state로 값을 이용하는 곳에서 사용하고 setState로 갱신을 하여 nextState 값을 가지게 하며 갱신 시의 과정은 react virtual dom에 의해 일어나게 되고 react virtual dom은 갱신 시 변경점을 파악하여 변화된 부분만 dom을 재랜더링시킨다.
const [state, setState] = useState(()=> initialState);
// 또는
const [state, setState] = useState(()=>{
...
return initialState;
})
지연 초기 state는 useState를 초기화 시키는 방법중 하나로서 useState의 초기값으로 즉시 실행함수를 넣고 값을 리턴 하게 함으로 state를 초기화 시키는 방법이다. 이 방법은 여러가지 특징을 가진다.
지연 초기 state의 특징
setState((prevState)=>nextState);
이전 state를 사용해서 새로운 state를 계산하는 경우 함수를 전달할 수 있다. 이 경우 함수는 이전 값을 받아 갱신할 값을 반환한 것으로 반환된 값으로 state를 갱신한다. 이렇게 함수를 사용하면 memorized 된 연산에서, prevState를 받아서 nextState로 갱신하기 때문에 deps에 해당 state를 넣어주지 않아도 state가 갱신되어서 문제 없이 사용할 수 있다.
const [state, setState] = useState([]);
const fn1 = useCallback(()=>{
setState(state.concat(1);
},[state])
const fn2 = useCallback(()=>{
setState((prevState)=>prevState.concat(1));
},[])
위 코드에서 fn1을 실행했을 때와 fn2를 실행했을 때 똑같이 배열의 끝에 1을 추가하는 연산이 일어나게되고 그 결과로 함수를 호출하기 전의 배열에서 끝에 1 원소가 추가된 형태로 state가 갱신되게 된다.
새로운 객체가 아닐때
const [state, setState] = useState([]);
const updateState = () => {
setState(state.push(1));
}
위의 코드에서 updateState 함수를 실행하면 버그를 일으키게 된다. react에선 새로운 객체가 아닐 경우, react diff 알고리즘에 비교 대상이 아니여서 리랜더링 되지 않기 때문에 재랜더링이 되지 않아 버그를 일으킨다.
setState가 한번에 두번 일어날 때
const [state, setState] = useState([]);
const updateState = () => {
setState(state.concat(1));// expect state === [1]
setState(state.concat(2));// expect state === [1,2]
}
console.log(state) // [2]
이 경우 처음의 setState가 끝나고 state가 갱신이 되어있지 않은 상태(state = [])이기 때문에 두번째 setState에서 state가 [] 상태로 setState가 일어남으로 최종적인 state 는 [2]가 되어 사용자가 의도했던 [1, 2]가 되지 않는다.
이유는 setState는 javascript 호출 스택이 전부 비워지고 나서 이루어지기 때문에 상태의 업데이트가 비동기적으로 이루어지게 된다. 따라서 호출 스택이 비워지고 난 뒤인 updateState 함수가 끝날 때, 호출 스택이 비워지기 전의 state( === [])를 가져와서 setState가 이루어지기 때문에 state는 [2]로 갱신된다.