[React] State 구조 선택하기

현수·2025년 3월 20일
0

State 구조화 원칙

오류 없이 상태를 쉽게 업데이트하기 위해서 공식 문서에서는 다음 원칙을 제시한다. 이 글을 읽기 전까지 나는 막연히 렌더링에 영향을 주는 값은 모조리 state로 관리하는 줄 알았다. 그러면서 실제로 모순되거나, 불필요한 state를 사용한 경험이 있다. state 구조화 원칙은 그런 문제가 될 요소들을 제거하는 방향으로 구성되어 있다.

리액트의 렌더링 로직을 잘 이해했다면 state 구조화 원칙 역시 쉽게 납득할 수 있을 것이다.

연관된 state 그룹화

  • 두 개 이상의 state를 항상 동시에 업데이트하는 경우 항상 동기화될 수 있도록 단일 state 변수로 병합해야 한다.

    ex. 마우스 커서의 x, y 좌표 등은 하나의 객체 state로 관리

state의 모순 피하기

  • 여러 state가 서로 모순되고 불일치하는 경우 피해야 한다.

    ex. isSentisSending state가 있을 때 isSent가 true이면서 isSending도 true인 경우는 존재할 수 없다. 다시 말해 모순된다.
    → typing, sending, sent 세 가지 유효한 상태 중 하나를 가질 수 있는 status state로 관리
    → 그 후 아래와 같이 각각의 상태에 대한 상수를 선언하면 서로 동기화되지 않을 우려를 지우면서 가독성을 높일 수 있다.

  const isSending = status === 'sending';
  const isSent = status === 'sent';

불필요한 state 피하기

  • 렌더링 중 컴포넌트의 props나 기존 state 변수에서 계산 가능한 정보는 별도의 state로 관리하지 않아야 한다.
  • 특히 props를 state에 미러링하여 사용하면, 부모 컴포넌트에서 나중에 props에 다른 값을 담아 보내도 state 변수는 업데이트되지 않는 문제가 발생할 수 있다.
  // bad
  function Message({ messageColor }) {
    const [color, setColor] = useState(messageColor);
  // good
  function Message({ messageColor }) {
    const color = messageColor;
  • 특정 props에 대한 모든 업데이트를 무시하기 원할 때에는 의도적으로 미러링을 사용할 수 있다.
    → 이 때에는 관례적으로 props 이름을 initial 또는 default로 시작하여 새 값이 무시됨을 명시한다.
  function Message({ initialColor }) {
    // The `color` state variable holds the *first* value of `initialColor`.
    // Further changes to the `initialColor` prop are ignored.
    const [color, setColor] = useState(initialColor);

state 중복 피하기

  • 배열의 어떤 한 원소에 대한 state를 생성하는 대신, 배열 요소의 고유한 값(id 등)에 대한 state를 생성하여 해당 state로 배열에 접근하는 것이 좋다.

  • 그 원소에 대한 정보가 중복되는 경우, 두 가지 정보를 모두 업데이트하는 것보다 원본 state만 변경하는 것이 효율적이고 더 안전하기 때문이다.

    ex. const [items, setItems] = useState([...]);가 선언되어 있을 때,
    const [selectedItem, setSelectedItem] = useState(items[0]); 대신
    const [selectedIndex, setSelectedIndex] = useState(0);을 사용하는 게 좋다.

깊게 중첩된 state 피하기

  • 깊게 중첩된 state는 아무리 스프레드 연산자를 사용하더라도 새 객체를 반환하는 코드를 작성하기 번거롭다.
  • 이런 경우 객체를 평탄화하거나, immer 라이브러리를 사용할 수 있다.
  • immer 라이브러리를 사용하면 객체의 값을 직접 수정하는 것처럼 간단하게 코드를 작성할 수 있다.
  • 아래와 같이 중첩된 객체와 배열로 state가 복잡해지는 경우, 전체 객체를 중첩하는 대신 자식 요소의 id 배열을 가지도록 조정하면 state를 평탄하게 유지할 수 있다.
  export const initialTravelPlan = {
    id: 0,
    title: '(Root)',
    childPlaces: [{
      id: 1,
      title: 'Earth',
      childPlaces: [{
        id: 2,
        title: 'Africa',
        childPlaces: [{
          id: 3,
          title: 'Botswana',
          childPlaces: []
        }, {
          id: 4,
          title: 'Egypt',
          childPlaces: []
        }, 
        ...
      ], 
    ...
  };
  export const initialTravelPlan = {
    0: {
      id: 0,
      title: '(Root)',
      childIds: [1, 42, 46],
    },
    1: {
      id: 1,
      title: 'Earth',
      childIds: [2, 10, 19, 26, 34]
    },
    2: {
      id: 2,
      title: 'Africa',
      childIds: [3, 4, 5, 6 , 7, 8, 9]
    },
    3: {
      id: 3,
      title: 'Botswana',
      childIds: []
    },
    ...
  };

0개의 댓글