Front-End

[React] state

Minch13r 2025. 5. 4. 13:17

1. State의 기본 개념 🧩

State란 무엇인가?

  • 컴포넌트 내부에서 관리되는 변경 가능한 데이터 저장소
  • 컴포넌트의 "메모리" 역할을 하는 객체
  • props와 달리 컴포넌트가 직접 제어하고 수정 가능
  • 상태가 변경되면 React는 해당 컴포넌트를 자동으로 다시 렌더링

State가 필요한 경우

  • 시간이 지남에 따라 변경되는 데이터
  • 사용자 입력에 반응해야 하는 값
  • API 호출 결과를 저장할 때
  • UI 상태(토글, 폼 입력값, 로딩 상태 등) 관리

State vs Props

  • State: 컴포넌트 내부에서 관리, 변경 가능
  • Props: 부모로부터 전달받음, 읽기 전용
  • State는 종종 자식 컴포넌트에 props로 전달됨

2. 함수형 컴포넌트의 useState Hook 🪝

useState 기본 문법

import React, { useState } from 'react';

function Counter() {
  // [현재 상태값, 상태 업데이트 함수] = useState(초기값)
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

useState 특징

  • 여러 개의 state 변수 선언 가능
  • 다양한 데이터 타입 저장 가능 (숫자, 문자열, 불리언, 객체, 배열 등)
  • 함수형 업데이트 지원 (이전 상태 기반 업데이트)
  • 지연 초기화(lazy initialization) 지원

복잡한 상태 관리

// 객체 형태의 상태
const [user, setUser] = useState({ name: '', age: 0 });

// 객체 업데이트 (불변성 유지)
setUser(prevUser => ({ ...prevUser, name: '민영' }));

// 배열 형태의 상태
const [items, setItems] = useState([]);

// 배열에 항목 추가
setItems(prevItems => [...prevItems, newItem]);

지연 초기화

// 무거운 초기화 작업은 함수로 전달하여 최초 렌더링 시에만 실행
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation();
  return initialState;
});

3. 클래스형 컴포넌트의 state 📦

초기화 및 사용법

class Counter extends React.Component {
  constructor(props) {
    super(props);
    // state 초기화
    this.state = {
      count: 0
    };
  }
  
  render() {
    return (
      <div>
        <p>현재 카운트: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          증가
        </button>
      </div>
    );
  }
}

setState 메서드 특징

  • 상태 업데이트는 비동기적으로 처리됨
  • 여러 setState 호출은 성능을 위해 배치 처리될 수 있음
  • 이전 상태에 의존하는 업데이트는 함수 형태로 작성해야 함
// 잘못된 방식 (비동기적 업데이트로 인한 문제 발생 가능)
this.setState({ count: this.state.count + 1 });

// 올바른 방식 (이전 상태 기반 업데이트)
this.setState(prevState => ({ count: prevState.count + 1 }));

상태 업데이트 후 작업 수행

this.setState({ count: this.state.count + 1 }, () => {
  // 상태 업데이트 후 실행될 콜백 함수
  console.log('상태가 업데이트되었습니다:', this.state.count);
});

4. 상태 업데이트 주의사항 ⚠️

불변성(Immutability) 유지

  • 상태를 직접 수정하지 말고, 항상 새 객체를 생성하여 업데이트
// 잘못된 방식 (직접 수정)
const [user, setUser] = useState({ name: '홍길동', age: 30 });
user.age = 31; // ❌ 직접 수정하면 리렌더링이 발생하지 않음
setUser(user);

// 올바른 방식 (새 객체 생성)
setUser({ ...user, age: 31 }); // ✅ 불변성 유지

객체와 배열 업데이트 패턴

// 객체 업데이트
const [person, setPerson] = useState({ name: '', address: { city: '', zipCode: '' } });

// 중첩 객체 업데이트
setPerson(prev => ({
  ...prev,
  address: {
    ...prev.address,
    city: '서울'
  }
}));

// 배열 업데이트
const [todos, setTodos] = useState([]);

// 항목 추가
setTodos([...todos, newTodo]);

// 항목 제거
setTodos(todos.filter(todo => todo.id !== idToRemove));

// 항목 수정
setTodos(todos.map(todo => 
  todo.id === idToUpdate ? { ...todo, completed: true } : todo
));

비동기적 업데이트 처리

// 문제가 될 수 있는 코드
const [count, setCount] = useState(0);

const handleClick = () => {
  setCount(count + 1); // count 값 참조
  setCount(count + 1); // 여전히 같은 count 값 참조
  // 결과적으로 count는 1만 증가
};

// 올바른 접근법
const handleClickCorrect = () => {
  setCount(prevCount => prevCount + 1); // 이전 상태 기반 업데이트
  setCount(prevCount => prevCount + 1); // 업데이트된 상태 기반
  // 결과적으로 count는 2 증가
};

5. 복잡한 상태 관리 기법 🔄

useReducer를 통한 복잡한 상태 로직 분리

import { useReducer } from 'react';

// 리듀서 함수 정의
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  // [현재 상태, 디스패치 함수] = useReducer(리듀서 함수, 초기 상태)
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  
  return (
    <div>
      <p>카운트: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

상태 끌어올리기(Lifting State Up)

  • 여러 컴포넌트가 동일한 상태를 공유해야 할 때 사용
  • 공통 조상 컴포넌트에서 상태를 관리하고 props로 전달
function Parent() {
  const [value, setValue] = useState('');
  
  return (
    <div>
      <ChildA value={value} onChange={setValue} />
      <ChildB value={value} />
    </div>
  );
}

커스텀 훅을 통한 상태 로직 재사용

// 커스텀 훅 정의
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };
  
  const reset = () => setValues(initialValues);
  
  return { values, handleChange, reset };
}

// 커스텀 훅 사용
function SignupForm() {
  const { values, handleChange, reset } = useForm({ username: '', email: '' });
  
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(values);
    reset();
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={values.username}
        onChange={handleChange}
      />
      <input
        name="email"
        value={values.email}
        onChange={handleChange}
      />
      <button type="submit">가입</button>
    </form>
  );
}

 

'Front-End' 카테고리의 다른 글

CDN(Content Delivery Network)  (1) 2025.08.06
[React] 클래스형 컴포넌트와 함수형 컴포넌트  (0) 2025.05.10
[React] Components와 Props  (1) 2025.05.02
[React] JSX  (1) 2025.05.01
[JavaScript] JQuery  (1) 2025.03.08