-
[React] useState() 훅과 클로저React 2024. 5. 6. 16:14
결론부터 말하자면 useState는 리렌더링 되었을 때 이전 상태를 기억하기 위해서 클로저의 상태 유지 특성을 사용합니다.
useState()의 구조
useState는 상태를 관리할 수 있는 훅으로 아래와 같은 방법으로 사용 가능합니다.
[state, setter] = useState(initialState)
useState는 초기값으로 initialState을 받고, [state, setter]의 배열을 반환합니다.
state는 상태를 나타내고 setter을 이용해서 state를 변경할 수 있습니다.
useState() 동작 원리
useState() 사용 예시를 봐볼게요.
useState(0) 호출 코드는 실행됩니다. state에는 초깃값 0이 할당됩니다.
클릭 이벤트를 통해서 handleClick() 함수가 실행되면 setter가 실행되고 => state 값은 1이 되고 => 리렌더링 됩니다.
그러면 다시 컴포넌트의 상단에서 부터 코드가 차례대로 실행되게 될 텐데 이때 어떻게 useState(0)에 의해서 state 값이 0으로 초기화되지 않고 이전 state 값 1을 기억할 수 있을까요?
useState가 반환한 setter(setState)는 useState의 호출이 끝났음에도 state가 저장되어 있는 외부 환경을 기억하고 있기 때문에 리렌더링 후에도 갱신된 state 값을 꺼내 올 수 있었습니다.
이렇게 매번 리렌더링으로 실행되는 함수 컴포넌트 환경에서 갱신된 state의 값을 유지하고 꺼내 쓰기 위해서 리액트는 클로저를 활용하고 있습니다.
클로저란 무엇일까요?
클로저에 대해 알아보기 전에 자바스크립트 동작 원리를 알아야 합니다.
자바스크립트에서 비동기 처리는 어떻게 이루어 질까? (+자바스크립트 동작 원리)
글을 쓰게 된 이유는 자바스크립트는 Single Thread로 동기적으로 동작하지만, 자바스크립트로 만들어진 웹 서비스들은 여러 작업들이 비동기적이고 동시에 동작하고 있는데서 오는 혼란에서 였습
dev-ea-jung.tistory.com
저번에 자바스크립트의 비동기 처리에 대한 글을 쓰면서 JS 동작 원리를 정리한 적이 있지만 한번 더 필요한 부분만 짚고 넘어 갈게요.
위 사진은 자바스크립트 엔진입니다. 보이는 것과 같이 메모리 힙과 콜 스택으로 구성되어 있습니다. 콜 스택에는 실행 컨텍스트가 쌓이게 됩니다. 실행 컨텍스트란 JS 코드가 실행되는 환경으로, 함수 호출(ex. foo())을 발견할 때마다 실행 컨텍스트가 생성되고, 콜 스택에 push() 되어 함수가 실행됩니다. 함수 실행이 끝나게 되면 pop() 됩니다.
예시와 함께 살펴볼게요.
위와 같은 코드가 있고 어떻게 동작할지 생각해 봅시다.
우선 전역 실행 컨텍스트는 아래와 같이 존재할 겁니다. a라는 변수와 outer라는 함수에 대한 정보를 담고 있습니다.
변수 객체라는 말이 조금 생소 할 수 있는데, 각 함수 실행 시에 해당 함수 내의 변수 및 함수 선언, 인자 등을 저장하는 객체로 변수 객체를 통해 함수의 스코프 및 클로저 동작이 관리됩니다.
쉽게 생각하면 다른 실행 콘텍스트들에서의 접근이 가능하게 만들어졌다고 정도만 생각해도 이어지는 설명을 이해하는데 무리가 없을 겁니다.
outer("한국") 함수가 먼저 호출 됩니다. outer("한국") 함수가 먼저 호출됨으로써 콜 스택에 outer 실행 컨텍스트가 쌓이게 됩니다.
이후 outer 함수의 동작이 끝나고 반환값을 return 해주고, 실행 컨택스트는 콜 스택에서 사라질 겁니다.
이후 outer의 반환값이 inner 함수 였기 때문에, 이 inner 함수를 전역 변수 객체에 존재하는 a 변수에 반환됩니다. 이렇게 서로 다른 실행 컨텍스트를 넘나들며 정보를 공유할 수 있게 해주는 것이 스코프 체인입니다.
이로써 a는 아래처럼 됩니다.
이러다 a(”철수”, 25) 를 만나게 되면 inner함수에 대한 실행 컨텍스트가 생성되고, inner 함수가 실행됩니다.
코드의 실행 결과는
제 이름은 철수 입니다. 저는 25살 입니다. 저는 한국에 살고 한국어를 사용해요.
여기서 생각해봐야할 내용이
name과 age는 파라미터로 넘겨받았지만, country와 language 정보는 outer 실행 컨텍스트에 존재하는데, outer 실행 컨텍스트는 실행을 끝내고 콜 스텍에서 pop 한 상태라는 겁니다.
그렇다면 어떻게
제 이름은 철수 입니다. 저는 25살 입니다. 저는 한국에 살고 한국어를 사용해요.
를 출력할 수 있었을까요?
정확히는 outer의 실행 컨텍스트만 사라진 것이고, outer 실행 컨텍스트의 변수 객체는 inner함수의 [[Scopes]]와 아직 연결되어 있기에, outer함수의 변수 객체는 살아있게 됩니다. 따라서 inner() 함수의 컨텍스트에서는 [[Scopes]]를 이용해 스코프 체인을 따라서 outer함수의 변수 객체 내의 country 변수와 language 변수에 접근이 가능한 것입니다.
이와 같이 이미 실행이 끝난 함수가(실행 컨텍스트가 사라진 함수, outer) 아직 살아있는 함수(콜 스택에 아직 올라가 있는 함수)의 스코프 체인에 의해 변수 객체가 유지되어 접근이 가능한 상황 일때, 아직 살아있는 함수(콜 스택에 실행 컨텍스트가 아직 올라가 있는 함수)를 클로저라고 부릅니다.
즉 위 코드에서는 inner 함수가 클로저가 되고, 이는 outer 변수 객체의 정보를 꺼내 쓸 수 있습니다.
다시 useState()와 Closure 연관 지어 생각하기
클로저의 원리를 생각하면서 다시 useState()의 상태 유지에 대해서 생각해 봅시다.
useState() 클로저(아직 콜스택에 있는 실행 컨텍스트)는 setState() 실행이 종료된 이후에도(setState()의 실행 컨텍스트가 사라진 후에도) 스코프 체인으로 인해 setState()의 변수 객체 안의 state 값을 계속 참조할 수 있기 때문에 리렌더링 후에도 갱신된 state 값을 유지하고 꺼내 사용할 수 있게 됩니다.
반응형'React' 카테고리의 다른 글
React로 Scroll Reveal Animation 구현하고 컴포넌트화 하기 (0) 2024.04.30 JSX는 어떻게 자바스크립트에서 변환될까? (0) 2024.04.23 React는 왜 등장하게 되었을까? (0) 2023.12.22 브라우저 렌더링 과정을 이해하려면 DOM을 알아야 한다? (0) 2023.12.20 메모이제이션을 이용해서 렌더링 성능을 개선할 수 있을까? (0) 2023.12.19