100log

About

React - useState, 가장 기본이자 비번히 사용하는 React 훅

React

React - useState

기본 소개

사용법

비동기성과 배칭(Batching)

상태 업데이트 규칙

배열 상태 다루기

불변성 유지의 중요성

성능 팁 & 자주하는 실수

궁금증

main-img

React - useState

기본 소개

useState는 React 훅(hook) 중 가장 기본이자 빈번히 사용하는 훅이다.
컴포넌트 내부에서 상태(state)를 선언하고, 그 상태를 업데이트할 수 있게 해주며 함수형 컴포넌트에서 로컬 UI 상태를 관리할 때 주로 사용한다.


사용법

import { useState } from 'react';

const Counter = () => {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>count: {count}</p>
            <button onClick={() => setCount(count + 1)}>증가</button>
        </div>
    );
};

export default Counter;
  • useState는 배열을 반환한다. 첫 번째 요소는 현재 상태값, 두 번째 원소는 그 상태를 바꾸는 함수(setter) 다.
  • 두 번째 원소(setter)는 통용적으로 set상태값(상태값이 name이라면 setName)으로 작명한다.
  • 초기값은 컴포넌트가 처음 렌더링 될 때 한 번만 사용된다.

비동기성과 배칭(Batching)

  • setState 호출은 즉시 DOM을 바꾸는 것이 아니라 React에게 "다시 렌더해줘"라고 예약하고 렌더 주기를 통해 적용된다.
  • React는 여러 개의 상태 업데이트를 한 번의 재렌더로 묶어(batch) 처리하며, React 버전과 상황에 따라 자동 배치 범위가 달라질 수 있으므로(React 18에서 자동 배치 확대) 함수형 업데이트를 활용하면 안전하다.
  • setState 다음 줄에서 상태값을 바로 읽으면 아직 이전 값일 수 있다. 상태 변경을 확인하려면 useEffect를 사용하자.

상태 업데이트 규칙

클래스형 컴포넌트의 this.setState()는 얇은 병합을 해준다. useState의 setter는 상태 전체를 대체하므로 객체를 업데이트하려면 이전 상태를 복사해야한다.

const [user, setUser] = useState({ name: 'Alice', age: 20 });

// 잘못된 예 — age가 사라짐
setUser({ name: 'Bob' });

// 올바른 예
setUser(prev => ({ ...prev, name: 'Bob' }));

업데이트가 이전 상태에 의존한다면 함수형 업데이트 패턴을 사용한다.

// 안전하지 않은 패턴 (특히 여러 번 호출하거나 비동기에서)
setCount(count + 1);
setCount(count + 1);


// 안전한 패턴
setCount(prev => prev + 1);
setCount(prev => prev + 1);

배열 상태 다루기

배열 상태를 직접 변형하면(예: push, splice) 안 된다. 항상 불변성 유지 방식으로 새 배열을 만들어서 갱신해야한다.

// 추가
setTodos(prev => [...prev, newTodo]);


// 수정
setTodos(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));


// 삭제
setTodos(prev => prev.filter(t => t.id !== id));

불변성 유지의 중요성

useState에서 배열 상태의 불변성을 유지해야 하는 주된 이유는 React의 효율적인 업데이트 및 리렌더링 메커니즘을 활용하기 위해서이다.
불변성을 지키면 상태 변경 시 이전 상태와 새로운 상태를 참조(메모리 주소)로 비교하여 변경된 부분만 효율적으로 업데이트할 수 있고, 예상치 못한 부작용(side effect)을 방지하며 예측 가능한 코드를 작성할 수 있다.

  • 성능 최적화: React는 상태가 변경되면 해당 컴포넌트를 리렌더링한다. 이때 이전 상태와 새로운 상태를 비교하여, 변경이 없는 부분은 다시 렌더링하지 않고, 변경된 부분만 업데이트하여 성능을 최적화한다. 만약 원본 배열을 직접 수정하면, React는 참조 주소의 변경이 없어 상태가 변경되었는지 감지하지 못하고, 올바른 업데이트가 이루어지지 않는다.
  • 안전한 상태 관리: 배열의 push(), pop() 등 원본 배열을 직접 수정하는 메소드 대신, 새로운 배열을 생성하는 spread 연산자(...), concat(), slice() 등의 메소드를 사용하면 원본 데이터를 훼손하지 않고 안전하게 상태를 변경할 수 있다.

성능 팁 & 자주하는 실수

  • 불필요한 상태 저장을 피하자: 다른 상태로부터 계산할 수 있는 값은 굳이 상태로 두지 말고 렌더링 시 계산하자.
  • 오브젝트 / 배열을 상태로 저장할 때 매번 새 참조를 만드는 코드 주의: 부모가 자주 재렌더되면 자식에 새로운 prop 레퍼런스가 전달돼 불필요한 렌더링이 발생할 수 있다. useMemo, useCallback 으로 최적화 가능.
  • 상태를 직접 변경하지 마라: 리액트가 변경을 감지하지 못하거나 버그가 발생한다.

궁금증

1. useState는 비동기인가?

  • 직접적으로 동기 / 비동기 함수가 아니지만, 동시성 관점에서 지금 즉시 값이 바뀌지 않기 때문에 비동기처럼 보인다. 실제로는 상태 변경을 예약하고 다음 렌더에서 반영된다.

2. setState가 같은 값으로 여러 번 호출되면 어떻게 되는가?

  • React는 최적화로 동일한 값이면 실제로 리렌더를 하지 않을 수 있다. 하지만 참조가 다른 객체/배열이면 리렌더링이 발생한다.

3. 상태를 깊게 중첩해서 써도 괜찮을까?

  • 쓸 수는 있지만 업데이트가 복잡해지고 실수하기 쉬워서 여러 개의 useState로 분리하거나 useReducer를 고려하자.
profile_img백종민
1993
Seoul
커리어 발전을 위한 새로운 기회를 찾고있습니다.