[모던 리액트 Deep Dive] 3장 리액트 훅 깊게 살펴보기

하니·2025년 7월 30일

React 길잡이

목록 보기
21/21

💠 useState

상태를 관리할 수 있게 해주는 훅

  • 초깃값이 없을 경우, 초깃값은 undefined

✔️ useState 사용 X > 동작하지 X는 코드1 : 일반 변수 사용
그냥 메모리에서만 바뀌고, React는 그걸 모르기 때문에 화면은 그대로 'hello'를 보여주게 된다. 리렌더링 발생 조건 충족 X

function Component() {
 let state = 'hello'
 
 function handleButtonClick() {
   state = 'hi'
 }
 return (
   <>
     <h1>{state}</h1>
     <button onClick={handleButtonClick}>hi</button>
   </>
 )
}

✔️ useState 사용 X > 동작하지 X는 코드2 : 리액트에서 렌더링이 일어나게 변경
매번 렌더링이 발생될 때마다 함수는 다시 새롭게 실행 > 새롭게 실행되는 함수에서 state는 매번 'hello'로 초기화된다.

function Component() {
 const [, triggerRender] = useState()
 let state = 'hello'
 
 function handleButtonClick() {
   state = 'hi'
   triggerRender()
 }
 return (
   <>
     <h1>{state}</h1>
     <button onClick={handleButtonClick}>hi</button>
   </>
 )
}

✔️ useState 구조

  • useState 훅의 결과값이 함수가 실행돼도 그 값을 유지할 수 있는 이유 > 클로저

    클로저 : 어떤 함수useState 내부에 선언된 함수setState가 함수의 실행이 종료된 이후에도useState가 호출된 이후에도 지역변수인 state를 계속 참조할 수 있다는 것

(실제 리액트 코드에서는 useReducer를 이용해 구현)

// [코드 3.16] useState 내부의 모습을 구현한 모습
// MyReact라고 불리는 클로저 내부에 useState와 관련된 정보를 저장해 두고,
// 필요할 때마다 꺼내놓는 형식으로 구성

const MyReact = (function () {
  const global = {} // 전역 객체에 모든 상태 저장 (모든 useState 값들을 순서대로 저장)
  let index = 0 // 현재 처리 중인 useState의 순서
  
  function useState(initialState) {
    if (!global.states) {
      // 애플리케이션 전체의 states 배열을 초기화한다.
      // 최초 접근이라면 빈 배열로 초기화한다.
      global.states = []
    }
    
    // states 정보를 조회해서 현재 상태값이 있는지 확인하고,
    // 없다면 초깃값으로 설정한다.
    const currentState = global.states[index] || initialState
    
    // states의 값을 위에서 조회한 현재 값으로 업데이트한다.
    global.states[index] = currentState
    
    // 즉시 실행 함수로 setter를 만든다.
    const setState = (function () {
      // 현재 index를 클로저로 가둬놔서 이후에도 계속해서 동일한 index에
      // 접근할 수 있도록 한다.
      let currentIndex = index // 현재 index를 클로저로 캡처
      return function (value) {
        global.states[currentIndex] = value // 항상 같은 위치에 저장
        // 컴포넌트를 렌더링한다. 실제로 컴포넌트를 렌더링하는 코드는 생략했다.
      }
    })()
    
    // useState를 쓸 때마다 index를 하나씩 추가한다. 이 index는 setState에서 사용된다.
    // 즉, 하나의 state마다 index가 할당돼 있어 그 index가 배열의 값(global.states)을
    // 가리키고 필요할 때마다 그 값을 가져오게 한다.
    index = index + 1
    return [currentState, setState]
  }
  
  // 실제 useState를 사용하는 컴포넌트
  function Component() {
    const [value, setValue] = useState(0)
    // ...
  }
  
  return { useState, Component }
})()
  • React는 호출 순서로 상태를 구분
    컴포넌트가 리렌더링될 때마다 같은 순서로 useState 호출
    첫 번째 useState는 항상 states[0], 두 번째는 항상 states[1]

❓ 컴포넌트 여러 개에서 useState를 쓰는데, 이때 모두 global에 순서대로 저장되어 있는걸까 ?
React는 컴포넌트별로 독립적인 훅 배열을 관리한다.

✔️ 게으른 초기화
useState의 인수로 원시값이 아닌, 특정한 값을 넘기는 함수를 인수로 넣어주는 것

  • 초깃값이 복잡하거나 무거운 연산을 포함하고 있을 경우 사용
    ex) localStorage나 sessionSotrage에 대한 접근, map, filter, find와 같은 배열에 대한 접근, 초깃값 계산을 위해 함수 호출이 필요할 경우 ...
// ❌ 매번 함수 실행 (값은 첫번째만 사용)
const [state, setState] = useState(expensiveFunction())

// ✅ 처음에만 함수 실행
const [state, setState] = useState(() => expensiveFunction())

💠 useEffect

profile
Hi, I am HANI Developer(╹◡╹). .....1hani me?

0개의 댓글