[FE] React Hook : useState

seunghee.Rho·2025년 2월 12일

FE

목록 보기
4/26
post-thumbnail

useState : 컴포넌트에 state 변수를 추가할 수 있는 React Hook

import { useState } from 'react'

const [state, setState] = useState(initialState)

📝 레퍼런스

useState(initaialState)

  • 배열 구조 분해를 사용하여 [something, setSomething]과 같은 state의 변수의 이름 지정

매개변수: initialState

  • state의 초기 설정값
  • 어떤 유형의 값이든 지정할 수 있음
  • 초기 렌더링 이후에는 무시된다.
  • initialState에 함수를 전달하면 이를 초기화 함수로 취급한다. 이 함수는 순수해야 하고 인수를 받지 않아야 하며 반드시 어떤 값을 반환해야 한다. 컴포넌트를 초기화할 때 초기화 함수를 호출하고, 그 반환값을 초기 state로 저장한다.

반환값

useState는 정확히 두 개의 값을 가진 배열을 반환한다.
1. 현재 state, 첫 번째 렌더링 중에는 전달한 initialState와 일치한다.
2. state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 set 함수

주의사항

  • useState는 Hook이므로 컴포넌트의 최상위 레벨이나 직접 만든 Hook에서만 호출할 수 있으며, 반복문이나 조건문 안에서는 호출할 수 없다.
  • Strict Mode에서 React는 의도치 않은 불순물을 찾기 위해 초기화 함수를 두 번 호출한다. 초기화 함수가 순수하다면 동작에 영향을 미치지 않고 호출 중 하나의 결과는 무시된다.

setState(nextState)과 같은 set 함수

useState가 반환하는 set 함수를 사용하면 state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있다. 여기에는 다음 state를 직접 전달하거나, 이전 state로부터 계산한 함수를 전달할 수도 있다.

매개변수: nextState

state가 될 값이다. 값은 모든 데이터 타입이 허용되지만, 함수에 대해서는 특별한 동작이 있다.

함수를 nextState로 전달하면 업데이터 함수로 취급한다. 이 함수는 순수해야 하고, 대기 중인 state를 유일한 인수로 사용해야 하며, 다음 state를 반환해야 한다.

반환값

set 함수는 반환값이 없다.

🔠 사용법

1. 컴포넌트에 state 추가하기

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(42);
  const [name, setName] = useState('Taylor');
  // ...

useState는 정확히 두 개의 항목이 있는 배열을 반환한다.

  1. 이 state 변수의 현재 state로, 처음에 제공한 초기 state로 설정된다.
  2. 상호작용에 반응하여 다른 값으로 변경할 수 있는 set 함수

화면에 내용을 업데이트하려면 다음 state로 set 함수를 호출한다.

function handleClick() {
  setName('seunghee');
}

⚠️ 주의사항
React는 다음 state를 저장하고 새로운 값으로 컴포넌트를 다시 렌더링한 후 UI를 업데이트한다.
set 함수를 호출해도 이미 실행 중인 코드의 현재 state는 변경되지 않는다❗️

function handleClick() {
  setName('seunghee');
  console.log(name); // 아직 "Taylor"입니다!
}

2. 이전 state를 기반으로 state 업데이트하기

age가 42라고 가정했을 때, 이 핸들러는 setAge(age + 1)를 세 번 호출한다.

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

최종 age는 45가 아니라 43이 된다.
이는 set 함수를 호출해도 이미 실행 중인 코드에서 age state 변수가 업데이트되지 않기 때문이다. 따라서 각 setAge(age + 1)의 호출은 setAge(43)이 된다.

이 문제를 해결하려면 setAge에 업데이터 함수를 전달하면 된다.

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

여기서 a => a + 1은 업데이터 함수이다.
이 함수는 대기 중인 state를 가져와서 다음 state를 계산한다.

React는 업데이터 함수를 에 넣는다. 그러면 다음 렌더링 중에 동일한 순서로 호출한다.

  1. a => a + 1은 대기 중인 state로 42를 받고 다음 state로 43을 반환
  2. a => a + 1은 대기 중인 state로 43을 받고 다음 state로 44를 반환
  3. a => a + 1은 대기 중인 state로 44를 받고 다음 state로 45를 반환

대기 중인 다른 업데이트가 없으므로, React는 결국 45를 현재 state로 저장한다.

규칙상 대기 중인 state 인수의 이름을 age의 a와 같이 state 변수 이름을 첫 글자로 지정하는 것이 일반적이다. 하지만 더 명확하다고 생각하는 다른 이름으로 지정해도 된다.

🆚 업데이터를 전달하는 것과 다음 state를 직접 전달하는 것의 차이점

1. 업데이터 함수 전달하기

import { useState } from 'react';

export default function Counter() {
  const [age, setAge] = useState(42);

  function increment() {
    setAge(a => a + 1);
  }

  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
    </>
  );
}

업데이터 함수를 전달하므로 +3 버튼이 작동한다.

2. 다음 state 바로 전달하기

import { useState } from 'react';

export default function Counter() {
  const [age, setAge] = useState(42);

  function increment() {
    setAge(age + 1);
  }

  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
    </>
  );
}

업데이터 함수를 전달하지 않으므로 +3 버튼이 의도한 대로 작동하지 않고, +1 된다.

3. 객체 및 배열 state 업데이트하기

state에는 객체와 배열도 넣을 수 있다. React에서는 state는 읽기 전용으로 간주되므로 기존 객체를 변경하지 않고, 교체해야 한다. 예를 들어, state에 form 객체가 있는 경우 변경하면 안된다.

// 🚩 state 안에 있는 객체를 다음과 같이 변경하지 마세요.
form.firstName = 'Taylor';
// ✅ 새로운 객체로 state를 교체해야 한다.
setForm({
  ...form,
  firstName: 'Taylor'
});

이 예시에서 form state 변수는 객체를 받는다.

import { useState } from 'react';

export default function Form() {
  const [form, setForm] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com',
  });

  return (
    <>
      <label>
        First name:
        <input
          value={form.firstName}
          onChange={e => {
            setForm({
              ...form,
              firstName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Last name:
        <input
          value={form.lastName}
          onChange={e => {
            setForm({
              ...form,
              lastName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Email:
        <input
          value={form.email}
          onChange={e => {
            setForm({
              ...form,
              email: e.target.value
            });
          }}
        />
      </label>
      <p>
        {form.firstName}{' '}
        {form.lastName}{' '}
        ({form.email})
      </p>
    </>
  );
}

각 input에는 전체 form의 다음 state로 setForm을 호출하는 change 핸들러가 있다. 전개 구문인 { ...form }은 state 객체를 변경하지 않고 교체한다.

4. 초기 state 다시 생성하지 않기

React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시한다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
  // ...

createInitialTodos()의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출한다. 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비일 수 있다.

이 문제를 해결하려면, useState에 초기화 함수로 전달하면 된다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  // ...

함수를 호출한 결과인 createInitialTodos()가 아니라 함수 자체인 createInitialTodos를 전달하고 있다. 함수를 useState에 전달하면 React는 초기화 중에만 함수를 호출한다.

🆚 초기화 함수를 전달하는 것과 초기 state를 직접 전달하는 것의 차이점

  const [todos, setTodos] = useState(createInitialTodos);

위 코드는 초기화 함수를 전달하므로, createInitialTodos 함수는 초기화 중에만 실행된다.
input에 타이핑할 때 같이 컴포넌트가 리렌더링할 때에는 실행되지 않는다.

const [todos, setTodos] = useState(createInitialTodos());

위 코드는 초기화 함수를 전달하지 않으므로, input을 타이핑할 때 같이 모든 렌더링에서 createInitialTodos 함수가 실행된다. 동작에 눈에 띄는 차이는 없지만 이 코드는 효율성이 떨어진다.

5. key로 state 초기화하기

컴포넌트에 다른 key를 전달하여 컴포넌트의 state를 초기화할 수 있다.

import { useState } from 'react';

export default function App() {
  const [version, setVersion] = useState(0);

  function handleReset() {
    setVersion(version + 1);
  }

  return (
    <>
      <button onClick={handleReset}>Reset</button>
      <Form key={version} />
    </>
  );
}

function Form() {
  const [name, setName] = useState('Taylor');

  return (
    <>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <p>Hello, {name}.</p>
    </>
  );
}

Reset 버튼이 version state 변수를 변경하고, 이를 form에 key로 전달한다.
key가 변경되면 React는 Form 컴포넌트를 처음부터 다시 생성하므로 state가 초기화된다.

출처: https://ko.react.dev/reference/react/useState

profile
Web Developer

0개의 댓글