Prop 변화를 효율적으로 상태에 반영하기

Hant·2023년 9월 29일
0

React

목록 보기
3/3
post-thumbnail

원문: Sharing logic between event handlers - React DEV

때때로, Prop의 변화를 일부 상태에만 반영하고 싶을 때가 있습니다. 예를 들어, List 컴포넌트는 items를 prop으로 받으며, selection 상태 변수로 선택된 아이템을 관리합니다. items prop이 변경될 때마다, selectionnull로 초기화해야 한다고 가정합시다.

interface Props {
  items: Array<Item>;
}

function List({ items }: Props) {
  const [isReverse, setIsReverse] = useState<boolean>(false);
  const [selection, setSelection] = useState<Item | null>(null);

  useEffect(() => {
    setSelection(null);
  }, [items]);
  
  // ...
}

위 방법은 매우 비효율적입니다. 부모로부터 변경된 items를 prop으로 받게 되면 어떤 일들이 일어날까요?

  1. List 컴포넌트와 그 자식 컴포넌트들이 렌더됩니다.
  2. React가 DOM을 업데이트합니다.
  3. useEffect가 실행되며, setSelection(null)가 호출됩니다.
  4. 1번의 리렌더가 다시 일어나고, 2번의 업데이트 또한 다시 발생합니다.

useEffect를 제거하고, 렌더링 도중에 상태를 바로 변경해봅시다.

const [isReverse, setIsReverse] = useState<boolean>(false);
const [selection, setSelection] = useState<Item | null>(null);

const [prevItems, setPrevItems] = useState<Array<Item>>(items);
if (items !== prevItems) {
  setPrevItems(items);
  setSelection(null);
}

이전 상태 값을 따로 저장하여 사용하면 이해하기 힘든 코드가 될 수 있지만, useEffect를 사용하는 것보다 좋습니다. 위 코드를 사용하여 변경된 items를 받는다면 어떤 일들이 일어날까요?

  1. List 컴포넌트 함수가 종료된 직후 바로 리렌더가 일어납니다. 때문에 자식들은 렌더되지 않으며, DOM 업데이트 또한 일어나지 않습니다.
  2. List 컴포넌트 리렌더 되며, 이번엔 자식 컴포넌트까지 렌더됩니다.
  3. React가 DOM을 업데이트합니다.

처음 패턴과 비교했을 때, 자식 컴포넌트 렌더링 1번, DOM 업데이트 1번을 단축할 수 있습니다. 이 패턴은 useEffect를 사용하는 것보다는 효율적이지만, 사실 이것조차 쓰지 않는 게 좋습니다. 어떤 방법을 사용하건, props나 다른 상태 값의 변화에 따라 일부 상태만을 조정하면, 데이터의 흐름이 이해하기 어려워지고 디버깅이 힘들어집니다. key를 사용해서 모든 상태를 초기화하거나, 렌더링 도중에 계산해서 사용할 수 있는지를 먼저 고려해 봐야 합니다.

예를 들어, Item 대신에 item의 ID를 저장하면 어떨까요? 선택된 ID가 새로운 items에 존재한다면, 렌더링 과정에서 이를 찾아 사용하면 됩니다.만약 존재하지 않는다면 null을 적용하면 됩니다. 처음과 동작이 동일하지 않은 것은 맞지만, 더욱 바람직한 동작인 것은 틀림 없습니다.

const [isReverse, setIsReverse] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<string | null>(null);

const selection = items.find(item => item.id === selectedId) ?? null;

// ...
profile
끊임없이 도전하는 프론트 개발자가 되고자 노력합니다.

0개의 댓글