[ React ] useState 뜯어보기 (feat. Closure) < 1 >

exceed_96·2024년 2월 17일
0

React

목록 보기
3/18
post-thumbnail

React 프로젝트를 진행하다보면 컴포넌트의 상태관리 혹은 데이터를 저장하기 위한 변수로 useState훅을 자주 사용한다. 이번 포스팅에서는 useState가 내부적으로 간략하게 어떻게 동작하고 Closure과 어떤 관련이 있는지 알아보자.

이번포스팅은 useState에 대해서 어느정도 사용해본 사람들의 기준에 맞춰서 작성된 포스팅이다.

1. useState훅이란?

useState훅은 컴포넌트의 렌더링 및 상태를 관리할 수 있는 훅이다.

useState훅을 단순히 변수 선언에 이용하는 훅으로 생각하면 안된다.

useState훅은 컴포넌트의 렌더링과 상태를 컨트롤 하는 훅으로써 사용한다는 점이다.


왜 이런용도로 사용할까?

React는 컴포넌트를 처음 렌더링 시킬때만 한번 탐색하고 그 후에는 별도의 명령을 주지 않는 이상 탐색하지 않는다.

즉, useState는 컴포넌트를 다시 탐색해서 렌더링에 변화를 주거나 컴포넌트의 상태를 관리하는 일종의 명령이다.



2. useState 사용법

useState를 사용하기 위해서는 첫번째로 해당 훅을 import해줘야 한다.

import { useState } from "react";

그 후, useState를 함수 컴포넌트 혹은 커스텀 훅 내부에 선언해서 사용하면 된다.

function ExamComponent(){

	const [value, setValue] = useState("apple")
}

useState훅의 반환값은 배열로 0번째 원소에는 useState훅의 상태변수에 정의된 값, 1번째 원소에는 0번째 원소를 바꿀 수 있는 set함수가 들어간다.

useState훅의 인수로는 초기값을 정의할 수 있다.

즉, 위 예시에서는 "apple"이 해당 useState훅이 정의될 때 초기값으로 정의되는 것이다.

인수를 정의하지 않으면 "undefined"값으로 초기값이 정의된다.


`useState`값을 바꾸고 싶으면 반환된 배열의 1번째 원소인 set함수를 이용하면 된다.
setValue("banana");

set함수의 인수에는 단순한 데이터 뿐만 아니라 콜백함수가 들어갈 수 있다.

setValue((prevState) => !prevState);

위와같이 인수로 콜백함수를 줄 경우에는 이전 state값에 의존성이 있을경우 사용하게 된다.


useState로 정의할 수 있는 데이터 타입에는 문자열, 숫자, 배열, 객체 등등 다양한 데이터 타입이 들어갈 수 있다.


여기까지는 useState가 "아 이렇게 사용하는 거구나"라는 마인드로 가볍게 지나갈 수 있지만 문제는 아래 생각에서 발생했다.

"배열의 0번째 원자로 해당 값을 받는다는건 어딘가에 해당 값을 저장해놨다가 가져온다는 얘기인데 어디에 저장하는걸까?"라는 의문이 들기 시작했다.

위 의문을 풀기 위해서 useState을 뜯어보기로 결심했다.



3. useState의 내부 로직

useState훅을 console.log로 출력해보면 어떤 값이 나올까?

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

위와같이 "resolveDispatcher()"등 처음보는 함수가 포함된 useState함수가 "react.development.js"소스파일에 있다는걸 확인할 수 있다.

해당 파일의 정확한 위치는 node_modules/react/cjs/react.development.js파일에 위치하고 있다.

여기서 "react.development.js"파일은 개발 환경에서 사용되는 JavaScript 파일이다.

개발자 도구의 console.log로 출력한 함수를 더블클릭하면 "Sources"탬으로 넘어가지면서 해당 함수가 위치한 소스파일의 이름을 볼 수 있다.


다시 본론으로 돌아와서 해당 함수의 구성을 자세히 살펴보자

function useState(initialState) {
  var dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

위 함수를 보면 초기값으로 initialState을 인자로 받는걸 확인할 수 있다.

이는 useState("apple")로 정의할 때 "apple"값이 initialState에 정의되어서 resolveDispatcher()함수의 useState메서드의 인수로 확인할 수 있다.


resolveDispatcher()함수를 살펴보자

function resolveDispatcher() {
  var dispatcher = ReactCurrentDispatcher.current;

  {
    if (dispatcher === null) {
      error('Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.');
    }
  } // Will result in a null access error if accessed outside render phase. We
  // intentionally don't throw our own error because this is in a hot path.
  // Also helps ensure this is inlined.


  return dispatcher;
}

해당 함수는 다시 ReactCurrentDispatchercurrent를 반환하는 걸 확인할 수 있다.

useState훅을 함수 컴포넌트나 커스텀 훅 내부에서 사용하지 않으면 에러 문구를 dispatcher가 null일 때 발생하는 걸로 보아 ReactCurrentDispatchercurrent가 해당 구분을 해주는걸로 확인할 수 있다.


그럼 한 단계 더 들어가서 `ReactCurrentDispatcher`를 살펴보자.
var ReactCurrentDispatcher = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: null
};

ReactCurrentDispatcher는 객체형태로 이뤄져 있는데 여기서 주의깊게 볼 점은 전역 변수로 선언되어 있다는 점이다.

즉, resolveDispatcher함수는 외부 즉, 전역변수인 ReactCurrentDispatcher에서 값을 가져오는 것이다.

이는 무엇을 의미하는 걸까?


바로 함수 내부 스코프에 있는 변수를 접근하는게 아닌 상위에 있는 스코프에 접근해서 state의 값을 가져오는 것이다. 즉 Closure를 이용하고 있는 것이다.

생각해보면 함수 컴포넌트도 결국 함수이다.

즉, 함수 컴포넌트가 선언될 시점(콜 스택에 실행 컨텍스트가 생성될 시점)에 접근이 가능했던 상위 스코프에 접근할 수 있는 것이다.

그래서 전역으로 선언되어 있던 state값들에 접근이 가능한 것이다.



4. 마치며

단순하게 사용만 했던 useState가 생각외로 꽤 복잡한?로직에 의해서 실행되는걸 확인할 수 있었다.

이번 포스팅은 1,2편으로 나눈 이유는 단순히 한 개의 포스팅으로 useState의 모든걸 정리하기에는 굉장히 길어질거 같아서 이번 포스팅에서는 useStateClosure와의 관계에 대해서 정리해 보았고 다음 포스팅에서는 useState가 실질적으로 돌아가는 내부 로직에 대해서 알아볼 예정이다.



5. Reference

https://velog.io/@ggong/useState-Hook%EA%B3%BC-%ED%81%B4%EB%A1%9C%EC%A0%80
https://velog.io/@zinukk/useState%EC%9D%98-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC
https://kyoung-jnn.com/posts/react-useState
https://goidle.github.io/react/in-depth-react-hooks_1/

profile
개발진행형

0개의 댓글

관련 채용 정보