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 알고리즘에 비교 대상이 아니여서 리랜더링 되지 않기 때문에 재랜더링이 되지 않아 버그를 일으킨다.
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]로 갱신된다.