icon안동민 개발노트

useState 심화


 useState 훅은 React 함수형 컴포넌트에서 상태를 관리하는 기본적인 방법을 제공합니다.

 이 절에서는 useState의 고급 사용법, 복잡한 상태 관리 기법, 그리고 성능 최적화 방법을 살펴보겠습니다.

객체와 배열 상태 다루기

 복잡한 상태를 다룰 때는 객체나 배열을 사용하는 경우가 많습니다.

 이 경우 불변성(immutability)을 유지하는 것이 중요합니다.

 객체 상태 업데이트

const [user, setUser] = useState({ name: 'John', age: 30 });
 
// 잘못된 방법
user.age = 31;
setUser(user);
 
// 올바른 방법
setUser(prevUser => ({ ...prevUser, age: 31 }));

 배열 상태 업데이트

const [items, setItems] = useState(['apple', 'banana']);
 
// 항목 추가
setItems(prevItems => [...prevItems, 'orange']);
 
// 항목 제거
setItems(prevItems => prevItems.filter(item => item !== 'banana'));
 
// 항목 수정
setItems(prevItems => prevItems.map(item => 
  item === 'apple' ? 'red apple' : item
));

이전 상태를 기반으로 한 업데이트

 상태 업데이트가 이전 상태에 의존할 때는 함수형 업데이트를 사용해야 합니다.

const [count, setCount] = useState(0);
 
// 잘못된 방법 (경쟁 상태 발생 가능)
setCount(count + 1);
 
// 올바른 방법
setCount(prevCount => prevCount + 1);

 이 방법은 여러 상태 업데이트가 동시에 일어날 때 특히 중요합니다.

상태 업데이트의 일관성 유지

 React는 상태 업데이트를 배치(batch)로 처리하여 성능을 최적화합니다.

 이로 인해 여러 상태 업데이트를 동시에 수행할 때 예상치 못한 결과가 발생할 수 있습니다.

const [count, setCount] = useState(0);
 
function increment() {
  setCount(prevCount => prevCount + 1);
  setCount(prevCount => prevCount + 1);
}

 이 경우, increment 함수를 호출하면 count는 2씩 증가합니다.

복잡한 상태 로직 분리

 상태 로직이 복잡해지면 리듀서(reducer)를 사용하는 것이 좋습니다.

 이는 useReducer 훅을 통해 구현할 수 있습니다.

const [state, dispatch] = useReducer(reducer, initialState);
 
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();
  }
}

지연 초기화

 초기 상태를 계산하는 데 비용이 많이 드는 경우, useState의 인자로 함수를 전달할 수 있습니다.

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

 이 함수는 컴포넌트의 초기 렌더링 시에만 실행됩니다.

성능 최적화

 불필요한 리렌더링을 방지하기 위해 다음과 같은 기법을 사용할 수 있습니다.

  1. 메모이제이션 : useMemouseCallback을 사용하여 계산 비용이 큰 값이나 함수를 메모이제이션합니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
  1. 상태 분할 : 큰 객체 상태를 여러 개의 작은 상태로 분할하여 관리합니다.
// 이렇게 하지 말고
const [user, setUser] = useState({ name: '', email: '', age: 0 });
 
// 이렇게 분할하세요
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);

useState 사용 시 주의사항

  1. 상태 업데이트는 비동기적 : 상태 업데이트 직후에 새 값을 읽으려고 하면 예상치 못한 결과가 발생할 수 있습니다.
  2. 불변성 유지 : 객체나 배열 상태를 직접 수정하지 말고, 새로운 객체나 배열을 생성하여 업데이트하세요.
  3. 함수형 업데이트 사용 : 이전 상태를 기반으로 업데이트할 때는 항상 함수형 업데이트를 사용하세요.
  4. 초기 상태 주의 : 컴포넌트가 리렌더링될 때마다 초기 상태 함수가 호출되지 않도록 주의하세요.

Best Practices

  1. 의미 있는 이름 사용 : 상태 변수와 설정 함수의 이름을 명확하게 지어 코드의 가독성을 높이세요.
  2. 관련 상태 그룹화 : 관련된 여러 상태를 하나의 객체로 그룹화하여 관리하세요.
  3. 불필요한 상태 피하기 : props나 다른 상태에서 계산할 수 있는 값은 상태로 관리하지 마세요.
  4. 상태 끌어올리기 : 여러 컴포넌트가 동일한 상태를 공유해야 할 때는 상태를 공통 부모 컴포넌트로 끌어올리세요.
  5. 상태 업데이트 로직 분리 : 복잡한 상태 업데이트 로직은 별도의 함수로 분리하여 관리하세요.
const updateUser = (prevUser, updates) => ({
  ...prevUser,
  ...updates,
});
 
setUser(prevUser => updateUser(prevUser, { age: 31 }));

 useState 훅은 간단하면서도 강력한 상태 관리 도구입니다.

 이 절에서 다룬 고급 기법들을 적절히 활용하면, 더 효율적이고 유지보수가 쉬운 React 애플리케이션을 개발할 수 있습니다.

 상태의 불변성을 유지하고, 함수형 업데이트를 활용하며, 필요한 경우 상태 로직을 분리하는 등의 방법을 통해 복잡한 상태도 효과적으로 관리할 수 있습니다.

 또한 성능 최적화 기법을 적용하여 애플리케이션의 반응성을 향상시킬 수 있습니다.

 useState를 사용할 때는 항상 React의 상태 관리 원칙을 염두에 두고, 애플리케이션의 요구사항에 맞는 최적의 상태 관리 전략을 선택하는 것이 중요합니다.