useState의 첫번째 매개변수로 state의 초기값을 설정한다. 그리고 컴포넌트 상태를 바꾸고 싶을 때마다 setState 함수의 첫번째 매개변수로 바꿔줄 값을 넘겨주면 다음 렌더링 시 새로운 상태가 컴포넌트에 반영된다. setState의 첫번째 매개변수로 함수를 넘겨주면 그 함수는 이전 상태를 매개변수로 해서 새로운 상태를 반환하는 형태여야 한다. 정리하면 아래와 같다.
초기값이 상태에 반영된다.
변경할 값을 setState 함수의 인수로 넘겨준다.
새로운 값이 상태에 반영된다.
[상태(state) 업데이트 과정]
useState가 반환하는 함수(setState)를 호출하면 상태를 업데이트할 수 있다. 하지만 setState 함수를 호출한다고 해서 상태가 바로 변경되는 것은 아니다. setState 함수는 상태 업데이트를 비동기로 수행한다. 즉, 상태 업데이트를 나중으로 예약하고 현재 JavaScript 호출 스택이 전부 비워지면 그때 실제로 상태를 업데이트한다.
[등장 배경]
- useState는 함수 컴포넌트에서 상태를 생성-수정-저장 할 수 있도록 함.
- useState가 제공하는 상태라는 개념은 JavaScript 함수 내 지역 변수와 비슷한 개념으로 컴포넌트가 마운트되고 언마운트 될 때까지 유지되는 값
- 하지만 JavaScript의 지역 변수는 함수가 반환되면 값이 사라진다는 점에서 컴포넌트 상태와 다름
- React 컴포넌트 내부에서 선언한 지역 변수는 컴포넌트가 렌더링되고 다시 렌더링되기 전까지만 유지 => Hook이 등장하기 전엔 함수 컴포넌트에선 상태를 따로 저장하고 유지할 수 없었음.
- 왜 컴포넌트 내부에 선언한 지역 변수 값은 컴포넌트를 업데이트(렌더링)할 때마다 유지되지 않을까? => 기본적으로 JavaScript 함수 내부에 선언된 지역 변수는 함수가 반환되면 사라진다. 그리고 React는 컴포넌트 상태(클래스 컴포넌트의 this.state 또는 함수 컴포넌트의 useState)가 변경되면 기본적으로 해당 컴포넌트부터 모든 자식 컴포넌트까지 render 함수를 실행하고 reconcilation을 수행한다.
- 함수 컴포넌트에서 render 함수는 함수 컴포넌트 자체이다.
- 이 상황에서 컴포넌트에 정의된 지역 변수는 매 render 함수가 실행될 때마다 다시 계산되고 할당된다. 그래서 컴포넌트 내부에서 지역 변수 값을 변경해도 다음 렌더링 시 항상 처음에 할당해 준 값이 나오는 것이다. 그래서 컴포넌트의 지역 변수는 어떤 변수의 alias를 설정하는 등 임시 값을 저장하는 용도로 사용하는 것이 좋다. 컴포넌트 지역 변수는 컴포넌트 내부에서만 의미 있게 사용될 수 있다.
[지역변수 사용 시 문제점]
위 함수 컴포넌트에서 count 값은 +1 증가 버튼을 클릭할 때마다 변수 값이 1씩 증가하긴 하지만, render 함수가 실행되지 않았기 때문에 변경 사항이 화면에 반영되지 않는다.
-그리고 React에서 reconcilation을 위해 각 컴포넌트의 render 함수를 실행하면 count 값은 다시 0으로 초기화되기 때문에 어짜피 count를 화면에 보여주는 용도로 사용할 수 없다.
- 그리고 count 값을 자식 컴포넌트에 props로 전달해도 render 함수가 실행되지 않았기 때문에 부모 컴포넌트의 count 값이 변했지만 자식 컴포넌트 props 값은 변하지 않는다.
이와 같은 이유로 지역 변수로는 함수 컴포넌트의 상태를 관리할 수 없기 때문에 useState Hook이 등장했다.
[useState 사용 예시]
위와 같이 useState 에 객체를 전달해서 여러 개의 상태를 관리할 수도 있다.
중요한 점은 비슷한 것은 객체로 묶어 놓고, 공통점이 없는 것끼린 분리해서 각 상태의 책임을 분산 -> 이래야 모듈화도 쉬워지고 불필요한 큰 객체 생성을 방지
- 여기서 책임은 변화를 의미, 서로 같이 업데이트 되는 상태끼린 한 객체로 묶어도 된다는 뜻 -> 예로, 마우스 x, y 좌표상태는 각각 관리하는 것보단 한 객체로 묶어서 관리하는 것이 효율적. 위와 같은 회원가입 데이터는 각각 따로 관리하는 것이 좋음
- 상태 업데이트 로직이 복잡하다면 useReducer를 활용해서 해당 컴포넌트로부터 상태 업데이트 로직을 분리하는 것이 컴포넌트 유지 보수 측면에서 좋음.
[주의할 점]
- useState는 상태를 값(value)로 관리 , 상태를 객체로 설정하고 해당 객체에 레퍼런스로 접근해서 필드 값을 변경하면 객체 필드 값은 실제로 변경되지만 이를 React가 알 수 없어 변경 사항이 화면에 반영되지 않음
- useState의 상태를 변경 할 때 항상 useState에서 제공하는 함수를 사용
- useState는 상태 업데이트 로직을 비동기로 실행하기 때문에 상태가 상태 업데이트 함수를 호출한 다음 줄에서 바로 업데이트 되지 않음 -> 상태 업데이트 후 코드를 실행하고 싶으면 아래와 같이 useEffect를 사용
useRef는 상태 업데이트를 동기로 진행하기 때문에 변경 사항이 바로 반영된다.