DEV Community

Chan
Chan

Posted on

미친 react key

map을 통한 렌더링

export function Parent() {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => [6,7,8,9,10,...prev]);
    }, 3000);
  }, []);

  return (
    <>
      {array.map((item) => (
        <Child key={item} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  console.log(item);

  return <>{item}</>
}
Enter fullscreen mode Exit fullscreen mode

실험 결과: 1 2 3 4 5

export function Parent() {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => [6,7,8,9,10,...prev]);
    }, 3000);
  }, []);

  return (
    <>
      {array.map((_, index) => (
        <Child key={index} item={item} />
      )}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

실험 결과: 6, 7, 8, 9, 10, 1, 2, 3, 4, 5

export function Parent() {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => [...prev.slice(0, 2), 10, ...prev.slice(3,prev.length)]);
    }, 3000);
  }, []);

  return (
    <>
      {array.map((item) => (
        <Child key={item} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  return <>{item}</>
}
Enter fullscreen mode Exit fullscreen mode

실험 결과: 10

export function Parent() {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => [...prev.slice(0, 2), 10, ...prev.slice(3,prev.length)]);
    }, 3000);
  }, []);

  return (
    <>
      {array.map((_, index) => (
        <Child key={index} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  return <>{item}</>
}
Enter fullscreen mode Exit fullscreen mode

실험 결과: 10

실험 1
상태: [1, 2, 3, 4, 5] => [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]
결과:

  • react.memo로 감싸지 않았을 경우:
    • key가 item인 경우: 리렌더링시 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력
    • key가 index인 경우: 리렌더링시 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력
  • react.memo로 감쌌을 경우:
    • key가 item인 경우: 리렌더링시 6, 7, 8, 9, 10 출력
    • key가 index인 경우: 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력

실험 2
상태: [1, 2, 3, 4, 5] => [1, 2, 10, 4, 5]
결과:

  • react.memo로 감싸지 않았을 경우:
    • key가 item인 경우: 리렌더링시 1, 2, 10, 4, 5 출력
    • key가 index인 경우: 리렌더링시 1, 2, 10, 4, 5 출력
  • react.memo로 감쌌을 경우:
    • key가 item인 경우: 리렌더링시 10 출력
    • key가 index인 경우: 1, 2, 10, 4, 5 출력

결론:

  • react memo를 사용하면 부모 리렌더링시, 동일한 key에 대해 prop이 변경되지 않는다면 자식의 리렌더링이 발생하지 않는다. 다만 prop이 변경되면 리렌더링이 발생한다.
  • react memo를 사용하지 않는다면 부모 리렌더링시 항상 자식의 리렌더링이 발생한다.

실험 2
부모 컴포넌트 상태: [1, 2, 3] => [3, 2, 1]
자식 컴포넌트 상태: text

export function Parent() {
  const [array, setArray] = useState([1,2,3]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => prev.toReversed());
    }, 3000);
  });

  return (
    <>
      {array.map((item) => (
        <Child key={item} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  const [text, setText] = useState("");

  return (
    <>{item}th textfield: 
      <input value={text} onChange={(event) => setText(event.target.value)}/>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

결과:

실험 2
부모 컴포넌트 상태: [1, 2, 3] => [3, 2, 1]
자식 컴포넌트 상태: text

export function Parent() {
  const [array, setArray] = useState([1,2,3]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => prev.toReversed());
    }, 3000);
  });

  return (
    <>
      {array.map((item) => (
        <Child key={item} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  const [text, setText] = useState("");

  return (
    <>{item}th textfield: 
      <input value={text} onChange={(event) => setText(event.target.value)}/>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
export function Parent() {
  const [array, setArray] = useState([1,2,3]);

  useEffect(() => {
    setTimeout(() => {
      setArray((prev) => prev.toReversed());
    }, 3000);
  });

  return (
    <>
      {array.map((item) => (
        <Child key={index} item={item} />
      )}
    </>
  );
}

export function Child({ item }: { item: number }) {
  const [text, setText] = useState("");

  return (
    <>{item}th textfield: 
      <input value={text} onChange={(event) => setText(event.target.value)}/>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

결과:

원인:

  • 이전렌더 key의 컴포넌트 종류 === 이후 렌더 컴포넌트 종류일 경우 unmount/mount하지 않는다. 실험 같은 경우 Child로 이전 렌더와 이후 렌더의 컴포넌트 종류 같아서 unmount/mount가 발생하지 않는다.
  • 이전 렌더 컴포넌트와 이후 렌더 컴포넌트에서 같은 종류의 컴포넌트 instance에 대해서는, state가 유지된다. 즉, key와 state가 매핑된다. 따라서 index를 key로 사용하였을 경우, array 순서 변경이 일어날 때 index와 자식 상태(text)가 매핑되어서 순서 변경이 제대로 일어나지 않는다. 반면 item을 key로 사용하였을 경우에는 array 순서 변경이 일어날 때 item와 자식 상태(text)가 매핑되어서 순서 변경이 정상적으로 같이 일어난다.

결론

  • index를 key로 사용해도 괜찮다. 다만 요소 순서의 변경이 일어날 때는 index를 key로 사용하면 안 된다.
  • 자식 컴포넌트의 상태가 key와 매핑되는데, index와 매핑되는 경우 요소 순서 변경이 일어나도 상태 순서 변경이 일어나지 않기 때문이다.

Top comments (0)