원티드 프리온보딩 프론트엔드 코스를 한지 벌써 한달이 다 되어가는데,
이번주 세션에서는 정말 흥미로운 내용이 많았다 ! (사실 매주 흥미로웠음 ㅎ)
세션중에 멘토님께서 계속 강조했던 부분은 redux던, react-query던 내부적으로 어떻게 돌아가는지 코드를 까봐라 ! 공부가 정말 많이된다 ! 였다.
그러던 중 듣고 있던 나에게 정말 흥미로운 내용을 던져주셨는데
"useState와 클로저 연관지어 설명해보라 " 였다.
사실 머리가 백지 그 자체 였지만 "당장은 아는척하고 넘어가고 모르면 얼른 찾아보고 공부하면 문제가 없다"하셨다. . ^^
모르지만 아는척하고 넘어갔으니 오늘은 useState의 원리와 클로저. 꼭 알고 넘어갈것 !
React Hook에서의 useState
react에서는 상태가 변경되면 변경되었음을 컴포넌트에 알려주고, 변경된 상태를 반영하여 컴포넌트가 리랜더링 된다.
클래스형 컴포넌트 에서는,
state를 정의하고 상태를 변경할 메소드 안에 setState 메소드를 추가하여 상태를 변경했다.
이때 state를 직접 변경하는 것이 아니라 setState를 사용하는 이유는 컴포넌트에게 랜더링이 필요함을 알리기 위해서다.
함수형 컴포넌트 에서는,
React Hooks인 useState를 사용하여 상태를 관리한다.
이는 initialValue를 받아서 [상태, 상태를 변경할 핸들러]를 반환한다. 비구조화 할당을 통해 아래의 형태로 사용한다.
const [state, setState] = useState(initialValue)
클래스형 컴포넌트는 render() 메소드를 통해서 상태변경을 감지하고 필요한 부분만 업데이트 할 수 있었다.
하지만 함수형 컴포넌트는 렌더가 필요할 때 마다 다시 함수를 호출한다 .
따라서 함수형 컴포넌트는 함수가 다시 호출됐을 때 이전 상태를 기억하고 있어야 하고, React Hooks는 이를 클로저를 통해 해결한다.
useState 작동
react 모듈 내부의 useState는 initialState를 인자로 받는 함수로 선언되어있다.
useState 함수는 resolveDisoatcher라는 함수를 통해 initialState를 전달한 결과를 리턴하고 있는데,
이 resolveDisoatcher라는 함수는 위와 같다.
ReactCurrentDispatcher의 current값을 리턴한다.
그럼 이 값은 뭐냐... 도대체.....
ReactCurrentDispatche는 전역에 선언된 current값을 담은 변수다.
클로저란?
함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 스코프에서 정의하는 것 <러닝 자바스크립트>
쉬운말로 하면 함수가 생성될 당시의 외부 변수를 기억하여 생성 이후에도 계속 접근 가능 한 것이다.
var outer = function() {
var a = 1;
var inner = function () {
console.log(++a);
}
inner();
}
outer();
outer에서 정의한 a라는 변수를 함수 바깥에서 접근하면 에러가 날 것이다.
하지만 함수 안에서 선언된 변수를 함수 안에 선언된 또 다른 함수 ( inner)에서 접근하는 것은 가능하다.
따라서 위 코드의 inner에서 a 변수에 접근 후 변수를 조작하는 것이 가능하다.
이 부분이 클로저의 정의와 맞닿아 있다. 내부 함수에서는 상위 함수 스코프의 변수에 접근 할 수 있는데, 이는 부모 함수가 이미 호출이 완료되어 리턴되었을 때도 가능하다.
이미 실행컨텍스트 큐에서 상위 함수의 컨텍스트 정보는 모두 사라졌음에도(실행완료 되었을때), 자식 함수가 아직 남아 있다면, 그 자식함수에서는 이미 실행이 종료된 부모 함수의 컨텍스트 정보를 참조할 수 있다 !
useState 메소드는 바로 이 클로저를 이용해서 함수의 상태를 기억한다.
useState는 외부에 선언된 상태값에 접근해서 이전 상태를 가져오고, 변경된 상태값을 관리한다.
함수형 컴포넌트도 결국 함수이기 때문에, 클로저를 통해 선언되는 시점에 접근 가능했던 외부 상태값에 계속 접근 할 수 있다.
리액트는 useState를 통해 상태를 접근하고 유지하기 위해서 useState메소드 바깥쪽에 state를 저장한다.
이 state들은 선언된 컴포넌트를 유일하게 구분할 수 있는 키이며, 배열 형태로 저장된다.
useState 안에서 선언되는 상태들은 이 배열에 순서대로 저장된다.
Hook의 규칙
출처: https://ko.reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
리액트 훅은 컴포넌트가 렌더링 될 때 마다 항상 동일한 순서로 호출이 보장되어야한다.
리액트 공식 홈페이지에서 설명한 위 와 같이 컴포넌트의 상태값들은 컴포넌트를 키로 하는 배열에 순서대로 저장되기 때문에, hook을 조건문이나 일반 js 함수 안에서 사용하게 된다면, 맨 처음 함수가 실행되었을 때 저장되었던 순서와 맞지 않게 되어 잘못된 상태를 참조하게 될 수 있다.
즉 , useState는 컴포넌트 내부에서 값을 변경시키는 것이 아니다. 외부에 있는 값을 변경시키기 때문에 상태가 변경된 직후 컴포넌트가 가진 값은 이전의 값을 그대로 참조한다.
이 부분을 알고나니 불현듯 스쳐지나가는 "왜 안될까?"기록
이노베이션캠프 파이널 프로젝트 (재료 제로웨이스트)에서 나는 datailpage 부분을 맡았고, 해당 페이지에 유저 팔로우 기능이 추가 되었다.
당시, 서버에서 받아온 isFollow의 ture/false 값을 useState를 통해 보여주고 set하고자 했다.
서버에서 받아온 값을 초기값으로 설정해주었다.
이렇게 구현해놓으니,,, 팔로우 버튼이 완전히 고장이 나버렸다.
버튼이 비활성화 되어있지만 "팔로우", 혹은 버튼이 활성화 되어 있지만 "팔로잉" 아주 난리였다.
뭐가 문제일까 고민하다가 useState를 제거했다
제거하고 나니 뭐가 문제였을까 싶을 정도로 깔끔하게 구현이 됐다.
뭐가 다른걸까 그때당시에는 몰랐지만 지금이라도 알아서 정말 다행이다.!!
❗️ useState는 컴포넌트 내부에서 값을 변경시키는 것이 아니다. 외부에 있는 값을 변경시키기 때문에 상태가 변경된 직후 컴포넌트가 가진 값은 이전의 값을 그대로 참조한다.❗️
Ref
<코어자바스크립트> 정재남
https://yeoulcoding.tistory.com/149#recentEntries
https://velog.io/@ggong/useState-Hook%EA%B3%BC-%ED%81%B4%EB%A1%9C%EC%A0%80