useState

# < useState 훅의 기본적인 형태 >

import React, { useState } from 'react';

const [state, setState] = useState(initalState);

 

useState 훅은 React에서 함수형 컴포넌트에서 상태 관리를 가능하게 하는 핵심 기능 중 하나이다. 이전에는 클래스 컴포넌트에서만 상태를 관리할 수 있었지만, React16.8 버전부터 도입된 Hook API 덕분에 함수형 컴포넌트에서도 상태를 유지하고 업데이트할 수 있게 되었다.

 

  • initialState: 컴포넌트의 초기 상태값입니다. 문자열, 숫자, 객체, 배열 등 어떤 값이든 될 수 있다.
  • state: 현재 상태값을 나타냅니다. initialState에 의해 초기화된다.
  • setState: 상태를 업데이트하는 함수이다. 이 함수에 새로운 상태값을 전달하면 컴포넌트가 리렌더링되면서 상태가 업데이트된다.

useState의 인수로 사용할 state의 초깃값을 넘겨준다.아무런 값을 넘겨주지 않으면 초깃값은 undefined다.

useState 훅의 반환 값은 배열이며, 배열의 첫 번째 원소로 state값 자체를 사용할 수 있고, 두 번째 원소인 setState 함수를 사용해 해당 state의 값을 변경할 수 있다. 

 

만약 useState를 사용하지 않고 함수 내부에서 자체적으로 변수를 사용해 상태값을 변경한다고 가정해 보자.

 

import React from 'react';

export default function AppKey() {
  let name = '프론트엔드';
  const handleClick = () => {
    name = 'hi';
  };
  return (
    <>
      <h1>{name}</h1>
      <button onClick={handleClick}>변경</button>
    </>
  );
}

 

위 코드를 런타임 하여 버튼을 눌러보면 아무런 반응이 없다는 것을 알 수 있다. 

 

리액트에서 환면에 보이는 내용을 바꾸고 싶을 때, 리액트는 "상태"라는 것을 사용해서 이를 관리한다. 상태란 컴포넌트의 정보를 저장하는 공간인데, 이 상태가 바뀌면 리액트는 자동으로 화면을 새로 그리게 된다.

 

하지만 만약 단순한 일반 변수를 사용한다면, 이 변수의 변경 사항을 리액트가 인지할 수 없다.

왜냐하면, 리액트는 상태를 통해서만 화면에 무엇을 그릴지 결정하기 때문이다. 일반 변수는 리액트의 "상태 관리 시스템"에 포함되지 않기 때문에, 변수가 변해도 리액트는 그 변경 사항을 감지하지 못하고 따라서 화면을 새롭게 그리지 않는다.

 

뭔 배은망덕한...

 

 

그렇다면, useState는 어떤 형태로 구현되어 있는지 알아보자.

실제 구현 코드와 다르지만, 대략적인 흐름을 같다.

 

let globalState = {}; // 전역 상태 저장소
let index = 0; // 현재 useState가 호출된 순서 추적

function useState(initialValue) {
  const currentIndex = index; // 현재 인덱스를 고정
  globalState[currentIndex] = globalState[currentIndex] === undefined ? initialValue : globalState[currentIndex];

  const setState = (newValue) => {
    globalState[currentIndex] = newValue;

    // 컴포넌트를 다시 렌더링하는 로직 필요 (여기서는 생략)
  };

  index += 1; // 다음 useState 호출을 위한 인덱스 증가

  return [globalState[currentIndex], setState];
}

function resetIndex() {
  index = 0; // 컴포넌트가 재렌더링될 때마다 인덱스 초기화
}

function MyComponent() {
  resetIndex(); // 컴포넌트가 렌더링될 때마다 인덱스 초기화

  const [count, setCount] = useState(0);
  const [text, setText] = useState('hello');

  // 컴포넌트의 나머지 부분
}

 

 

useState의 내부 코드를 살펴보면 어떻게 함수의 실행이 끝났음에도 함수가 선언된 환경을 기억할 수 있는 이유는 바로 클로저가 사용되었기 때문이다. 

 

좀 더 쉽게 이해해 보자.

 

위의 코드에서 useState라는 함수가 만들어낸 setState라는 기능을 보면, 마치 마법처럼 함수가 끝나고 나서도 어떤 정보를 기억할 수 있는데, 이러한 일이 가능한 이유는 '클로저'라는 특별한 기능 때문이다.

 

예를 들어, 한 상자 안에 작은 상자가 있는데, 작은 상자가 큰 상자 안의 물건들을 볼 수 있는 것과 비슷하다. 큰 상자가 함수를 만든 환경이고, 작은 상자가 그 안에서 만들어진 또 다른 함수다. 작은 상자(함수)는 큰 상자(환경) 안의 것들을 볼 수 있다.

 

useState 함수 안에서 setState라는 작은 함수가 만들어지는데, 이 setState 기능은 useState 함수가 끝난 후에도 useState 함수 안에서 쓰인 변수들, 예를 들어 currentIndex와 globalState 같은 것들을 계속해서 볼 수 있다. 

이것이 바로 클로저 덕분이다. 즉, setState는 마치 마법처럼 useState 함수가 끝나고 나서도 그 함수가 만들어진 환경을 '기억'할 수 있는 거다.

 

위의 코드에서는 useState를 여러 번 호출할 때 각각의 호출이 서로 다른 값을 '기억'하도록 해서, 내가 만든 프로그램에서 여러 가지 정보(상태)를 관리할 수 있게 해주는 것이다.

 

# < 게으른 초기화 >

useState에서 초기 상태를 설정할 때, 보통은 useState()의 인자로 초기값을 바로 넣는다. 하지만, 초기 상태를 계산하는 데 많은 자원이 필요한 경우, 함수를 인자로 넘겨 게으른 초기화를 할 수 있다. 이 방법은 초기 상태 값이 실제로 필요할 때까지 그 계산을 미룬다. 이는 리렌더링 될 때마다 상태 값을 계산하는 것이 아니라, 필요할 때에만 해당 계산이 실행되게 만든다.

간단히 말해, 리액트에서는 컴포넌트가 렌더링될 때마다 함수형 컴포넌트가 다시 실행된다. 이 때, useState에 함수를 인자로 넣으면, 이 함수는 컴포넌트가 처음 렌더링될 때 단 한 번만 실행된다.

 

이는 초기 상태를 설정할 때 복잡하거나 리소스를 많이 사용하는 계산이 필요한 경우, 해당 계산을 최초 렌더링 시에만 수행하고 그 후에는 실행되지 않도록 함으로써 성능을 최적화하는 데 도움이 된다. 

 

const [state, setState] = useState(() => {
  // 복잡한 계산 로직
  const initialState = performComplexCalculation();
  return initialState;
});

 

'React' 카테고리의 다른 글

useEffect  (0) 2024.04.24
input태그와 onChange함수의 관계  (1) 2024.04.23
리액트에서 Key란?  (1) 2024.04.19
리액트의 렌더링이란  (1) 2024.04.19
(흥미) useRef로 input 대체하기  (0) 2024.03.29