[ React ] useState는 어떻게 동작할까

DD·2021년 5월 9일
152

React

목록 보기
1/4
post-thumbnail

📣 들어가기 앞서

이 글은 useState를 사용하는 방법이나,
어떻게 하면 더 효율적으로 사용 할 수 있는지에 대한 how to와 관련된 글이 아님을 밝힙니다.

이 글의 목적은

  • useState가 어떻게 상태를 변경시키고,
  • 어떻게 컴포넌트 함수가 변경 시킨 값으로 렌더링을 진행하는지

대략적으로 설명해보고자 합니다. 아주 추상화해서 설명할 예정이니 실제로 이렇게 동작한다기보다, 이런 컨셉(?)으로 동작하는구나 정도로만 이해해주세요.

아주 기초적인 내용이라 리액트를 처음 접하시는 분이 대상입니다!!


✏️ 이 글을 쓰게 된 이유.

아주아주 간단한 예시입니다.

이 코드를 실행하고, div태그를 두 번 클릭하면 console.log가 어떻게 찍힐지 예상하실 수 있나요?
(있다면 뒤로가기를....)


(못생긴 예시 ㅎㅎ..)

첫 번째 클릭에서 click!!이 기록되고, 두 번째 클릭에서 '실행될까??'가 기록되었습니다.
혹시 첫 번째 클릭부터 '실행될까??'가 콘솔에 기록될거라 생각하신분이 계시다면, 이 글을 계속 읽어주세요.
(아니라면.. 뒤로가기를....)

이 글을 쓰게 된 이유가 바로 이 지점입니다.
아시다시피 useState는 배열을 반환하며,

  • 배열의 첫 번째 요소는 <상태 값 저장 변수>(이하 state),
  • 두 번째 요소는 <상태 값 갱신 함수>(이하 setState)입니다.

우리는 이 배열을 비구조화 할당을 통해 이렇게 뽑아 사용하죠.

const [state, setState] = useState(initiailState)

하지만 간혹, 이 setState가 state를 변경시킨다고 오해하시는 분들이 있는거 같아서 이 글을 작성하게 되었습니다! (TIL만 가득한 블로그 포스팅도 할겸..).
생각해보세요, 우리는 state를 const로 선언했습니다. 값이 변할리 없죠!😎😎

그럼 useState가 내부적으로 어떻게 동작하는지, 우리는 어떻게 변경된 상태값을 컨트롤하며 컴포넌트를 리렌더링 하는지 알아봅시다.

❓ useState의 반환값은 어디에서 올까?

(이 부분은 적당히 보고 넘어가세요!)

react module

import {useState} from 'react'

먼저 import 문부터 살펴보죠.
우리는 'react'라는 모듈에서 useState를 named import해서 사용하고 있습니다.

위 사진은 node_modules/react/cjs/react.development.js 내부에 각종 hooks 함수가 선언된 곳입니다. useState는 dispatcher라는 인스턴스를 생성하고, 인자로 초기값을 받아 dispatcher.useState에 전달후 반환값을 return합니다. 즉

  • dispatcher의 메소드 useState에 initialState를 전달하면 배열을 반환하고
  • 그 안에 우리가 사용할 state와 setState가 있다는 이야기입니다.

뒤에서 더 간단하게 정리해볼 예정이니 일단 그런가보다 하고 넘어가겠습니다.
더 거슬러 올라가서 dispatcher를 반환하는 resolveDispatcher 함수를 찾아보죠.


이 함수는 어디선가 dispatcher를 가져오고 에러처리를 하네요.
새로운 키워드로 ReactCurrentDispatcher가 나왔습니다. 좀 더 거슬러 올라가보겠습니다. 🐋


자, 이제 다왔습니다. ReactCurrentDispatcher라는 객체는 전역에 선언된 녀석이고, 속성으로 current를 가지고 있네요. 이 current가 우리가 찾던 dispatcher가 담길 곳입니다. 언제 어떻게 dispatcher가 담기는지는 넘어가겠습니다. 여기서 더 깊게 들어가면 리액트를 아예 뜯는 사태가 벌어질테니..

3줄요약

  • useState를 포함한 hooks는 react 모듈에 선언되어있는 함수이고,
  • 실행 될 때 마다 dispatcher를 선언하고 useState 메소드 실행해서 그 값을 반환한다.
  • 할당부를 거슬러 올라가니 dispatcher는 전역 변수 ReactCurrentDispatcher로부터 가져온다

뭔가 떠오르지 않나요? 함수가 선언부보다 상위에 있는 값에 접근하는 것... 바로 Closure입니다.

혹시 이 과정을 더 자세히 알고 싶으시다면 이 글을 추천드립니다.
React 톺아보기 - 03. Hooks_1



❓ setState 함수는 어떻게 상태를 변경시키나?

다시 처음에 작성한 예시와, 아주아주 간단하게 작성한 react 모듈 코드를 예로 들겠습니다.

App 컴포넌트 함수는 실행되면 먼저 useState를 호출해서 반환값을 비구조화 할당으로 추출해 변수에 저장합니다.
여기서 중요한건, App도 함수라는 겁니다. jsx를 반환하는 함수일뿐이죠. 렌더링이 시작되면 이 함수가 호출되어 새로운 jsx을 반환합니다.

우측의 react 모듈 코드를 보면, useState 밖에 전역으로 선언된 _value가 있습니다. 우리가 useState를 통해 관리하는 '상태'는 바로 이녀석입니다. setState는 App함수에 선언된 state가 아니라, 자신이 선언된 위치에서 접근할 수 있는 _value를 변경합니다! 이는 closure의 개념을 알면 바로 이해할 수 있으리라 생각합니다.

👉 웹이 로딩되고 최초로 App함수가 호출

  • App은 인수로 0을 전달하며 useState를 호출합니다.
  • useState는 실행될 때 마다 초기값을 전달받지만, 내부적으로 _value값이 undefined인지 확인해서, 최초의 호출에만 초기값을 _value에 할당하고, 이후 초기값은 사용되지 않습니다.
  • 이후 _value와 그 값을 재할당하는 setState 함수를 배열에 담아 반환합니다.

👉 setState 호출

  • 전달 받은 값 1을 react 모듈 상단의 _value에 할당합니다.
  • 이 코드에는 나와 있지 않지만, 컴포넌트 리렌더링을 trigger합니다.

👉 setState가 실행되어 리렌더링이 발생

위에서 리렌더링 과정에서 해당 컴포넌트 함수가 실행되고, 새로운 jsx를 반환한다고 정리했습니다.
setState가 리렌더링을 트리거하며 App함수가 두 번째로 실행되었을 때

  • 다시 인수 0을 useState에 전달하며 호출합니다.
  • useState는 내부적으로 _value값을 확인하고, undefined가 아닌 값이 할당되어 있기 때문에 초기값 할당문을 실행하지 않습니다.
  • 이후 useState가 현재 시점의 _value와 setState를 반환합니다. (이 시점에서 _value는 1입니다.)
  • 두 번째 실행된 App 함수 내부에서 useState가 반환한 값을 비구조화 할당으로 추출해 변수에 할당합니다.

즉, setState 함수는 자신과 함께 반환된 변수를 변경시키는게 아니라(const!!), 다음 useState가 반환할 react 모듈의 _value를 변경시키고, 컴포넌트를 리렌더링 시키는 역할을 합니다. 변경된 값은 useState가 가져옵니다!

❗ 마무리

  • 위 예시는 react로직을 아주 단순히 표현했을 뿐, 실제와 많이 다릅니다. useState를 여러번 사용해도 각기 다른 상태 값과 갱신 함수를 사용할 수 있는건 단순히 _value가 아니라 여러 값을 저장하고 있기 때문입니다. 이에 관해서는 다음에 다루어보겠습니다.
  • 대략 클로저를 활용해서, 이런 흐름으로 이어지는구나~ 정도만 이해하시길!
  • 요점은 setState가 state를 변경시키는게 아니기 때문에, setState 호출 이후 로직에서도 state의 값은 이전과 동일하다는 점입니다. 변경된 값은 다음 컴포넌트 함수가 실행될 때 useState가 가져옵니다!
  • 저도 아직 공부하고 있는 입장으로 부족한 부분, 틀린 부분에 대해 지적해주시면 수정하겠습니다!
  • 마지막으로 처음 클릭 예시의 코드 흐름을 대략적으로 확인해보면서 마무리하겠습니다.



최초 렌더링 - 첫 클릭 이벤트 발생


setState 실행

setState로 인한 리렌더링 - 두 번째 클릭 이벤트 발생

이후 두 번째 리렌더링(세번째 App함수 실행)이 일어나겠지만, 생략하겠습니다!

profile
기억보단 기록을 / TIL 전용 => https://velog.io/@jjuny546

14개의 댓글

comment-user-thumbnail
2021년 5월 12일

setState 는 state를 다음번에 렌더링 될 state를 set 할 뿐,
현재 렌더링 시점에서의 state를 변경시켜주지는 않는다는 걸까요??
글 너무 잘 읽었습니다.

1개의 답글
comment-user-thumbnail
2021년 5월 16일

재밌게 잘 봤습니다. 리액트는 알면 알수록 구조가 참 재밌게 만들었다는 생각이 드네요

답글 달기
comment-user-thumbnail
2021년 5월 16일

클로저가 어디에 쓰이나 했는데 이런 곳에 쓰이고 있었군요. 너무 좋은내용 감사합니다!!

답글 달기
comment-user-thumbnail
2021년 5월 16일

결과는 알고있었으나 usestate동작방식에 접근하는 방식이 많은 도움이 되었습니다.

궁금하면 뜯어봐야겠군요!!

답글 달기
comment-user-thumbnail
2021년 5월 17일

useState를 사용하면서 이 함수의 동작 원리가 궁금했었는데, 이렇게 좋은 글 작성해주셔서 감사합니다. 덕분에 의문을 해결할 수 있었습니다.

답글 달기
comment-user-thumbnail
2021년 5월 17일

잘 읽고갑니다 useState에 대해 좀 더 자세히 알아가요

답글 달기

글이 너무 좋아서 트윗했습니다 잘봤습니다 감사합니다 ㅎㅎ

답글 달기
comment-user-thumbnail
2022년 3월 8일

잘보았습니다 감사합니다

답글 달기
comment-user-thumbnail
2022년 3월 19일

잘 읽고 갑니다. 덕분에 useState()이해에 큰 도움이 되었어요!

답글 달기
comment-user-thumbnail
2022년 6월 15일

setState를 쓴 앞뒤의 state를 출력해봤는데, 같은 값이 나오길래 찾아왔습니다!
알기 쉽게 설명해주셔서 감사합니다 :) 공부 더 열심히 해야겠네요;;

답글 달기
comment-user-thumbnail
2022년 8월 19일

useState가 어떻게 작동하는지 정말 쉽게 이해됐습니다. 감사합니다
setState 함수를 실행하면 state 값을 재할당하는 것이 아닌(const로 선언되어 재할당할 수 없음)
리렌더링을 시켜 state 자체를 새 값으로 선언해버리는 거군요
참 만든 사람도 똑똑하네요..

답글 달기
comment-user-thumbnail
2022년 9월 1일

useState의 동작원리를 너무 잘 설명해주셔서 감사합니다 잘 읽고갑니다!

답글 달기