본문 바로가기
개념 정리/React

리액트에서 key 설정 (왜 map 함수에 전달되는 콜백 함수의 인수인 "index"값을 사용하면 안되는가?)

by 내머린빙글지구는둥글 2020. 11. 26.

안녕하세요, 처음코딩입니다.

오늘은 리액트에서 key를 설정할 때 겪었던 문제와, 문제를 해결하기 위해 시도했던 방법과 결론, 그리고 과정 속에서 느낀 점을 정리하려고 합니다.

혹시 리액트를 사용하시다가 개발자 도구 콘솔에서 "key" prop이 없다는 경고 메시지를 보신 적 있으신가요?

아마 리액트를 공부하시는 분이라면 한 번쯤은 만나지 않았을까 싶습니다.

아래의 에러를 경험하고 계신다면, 이제 문제를 해결하고 성장할 시간입니다!


겪었던 문제 :

리액트에서 배열을 매핑하여 props로 내리는 과정에서 문제가 발생했습니다.

일단 구동은 되는 듯하나, 개발자 도구 콘솔창에서 "key" prop이 없다는 에러 발생했습니다.


해결하기 위해 시도했던 방법 : 

1. MDN을 참고하여 map 함수에 전달되는 콜백 함수의 두 번째 인자인 index를 사용하여 "key" prop의 값으로 넣었습니다.

에러 메시지가 사라져 해결된 줄 알았으나 블로그 곳곳에서 특별한 경우가 아니라면 index값을 사용하면 안된다는 글을 보게 됩니다.

"key" prop에 index값을 할당


2.  배열안의 원소를 객체로 바꾸어 객체마다 고유한 아이디 값과 기존의 값을 넣어줬습니다.

마침내 에러도 해결하고 index가 아닌 고유값을 만들기도 하였습니다.

index를 쓰면 안되는 이유는 배열이 변경될 때 효율적으로 리렌더링을 하지 못한다는 정도만 알고 넘어갔습니다.

그리고 오늘 페어 프로그래밍을 하던 중, 페어분께서 key 설정에 대한 의문을 품으셨고 저는 확실한 제 의견을 제시할 수 없었습니다.


3. index를 사용하면 안되는 이유를 찾기위해 리액트 공식문서를 살펴보았습니다.

공식문서에서 제가 찾은 단서들은 다음과 같습니다.

1. "인덱스를 key로 사용할 경우 부정적인 영향에 대한 상세 설명"에 링크된 Robin Pokorny's 블로그

링크된 블로그를 통해서"응용 프로그램이 손상되고 잘못된 데이터가 표시 될 수 있습니다!" 라고 알게되었지만 왜 그런지 조금 더 이해하고 싶었습니다.


2. 리액트 공식 문서에서 Key에 대한 설명

리액트에서는 어떤 항목을 변경, 추가 또는 삭제할지 식별해주는 중요한 역할을 Key가 담당하는 것을 알게되었으나 아직은 index의 사용이 안티 패턴인 이유를 실감하긴 어려웠습니다.


3. 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

그 이유는 "재조정" 에서 찾을 수 있었습니다.

2번처럼 리액트에서 컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용된다고 합니다.

근데 만약 인덱스를 key로 사용하면, 항목의 순서가 바뀌었을 때 key 또한 바뀔 것입니다.

그 결과로, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수도 있습니다.

코드로 생각하면 다음과 같습니다.

import React, { useState } from "react";

const App = () => {
  
  const [movieLists, setList] = useState([
    { title: "Captain America" },
    { title: "Iron Man" },
    { title: "Hulk" },
  ]);

  const addMovie = () => {
    setList([{ title: "Thor" }, ...movieLists]);
  };

  const delMovie = () => {
    setList(movieLists.filter((movie) => movie.title != "Captain America"));
  };

  return (
    <>
      <input type="button" value="추가" onClick={addMovie} />
      <input type="button" value="삭제" onClick={delMovie} />

      <h2> MovieList </h2>
      {movieLists.map((list, index) => (
        <div key={index}>
          {list.title}, idx: {index} <input type="text" />
        </div>
      ))}
    </>
  );
};

export default App;

여기서 Captain America의 input태그에 값을 넣고 추가 버튼을 클릭하게 되면,

addMovie 함수가 실행되어 배열에 첫 번째 인자로 { title : "Thor" }가 들어가고 스프레드 연산자를 통해 나머지 원소들이 들어올 것입니다.

상태가 변화했으니 업데이트가 되니 리렌더링 됩니다.

이 때, 리액트는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 아래와 같은 결과가 나옵니다.


왜냐하면 리액트 입장에서는 key를 기반으로 갱신되고 재사용하니, key가 0인 Thor의 input 태그에 값을 넣게 된 것입니다.

삭제의 경우도 마찬가지로 key를 기반으로 갱신되니 값이 옮겨질 것입니다.

어떤 결과가 나올지 한 번 상상해보시고 직접 코드를 옮겨서 실행해보시기를 추천드립니다!

드디어 "응용 프로그램이 손상되고 잘못된 데이터가 표시 될 수 있습니다!" 라는 문장을 이해할 수 있게 되었습니다.


과정에서 느낀 점  :

리액트를 공부하면서 단순히 코드만 작성하고 구동시키는 것만이 중요한 게 아니라, 원리에 대해 이해하는 시간이 필요하다는 것을 깨달았습니다

Key에 대한 개념을 익히기 위해서 Life Cycle의 개념도 필요했고, 조화과정, Virtual DOM에 대해서도 이해하는 시간이 필요했습니다.

단순한 작동 코드보다 조금 더 본질에 가깝게 접근하는 시간이 필요했고 결과적으로 리액트를 더 이해하는데 도움이 되었습니다.

마치 처음 사귄 친구를 만날 때 재밌는 게임을 같이하는 것도 좋지만,

진솔하게 속 얘기를 하는 시간을 통해 서로를 더 잘 알아갈 수 있는 것처럼

저는 오늘 리액트라는 친구와 진솔한 얘기를 하며 조금 더 이해할 수 있게 되었습니다.

오늘의 경험으로 앞으로도 새로운 친구를 만나게 되면 급하게 친해지려고 하지않고 진솔한 얘기를 나눌 수 있는 시간을 갖게 될 거 같습니다.


Reference

리액트 공식문서 - ko.reactjs.org/docs/lists-and-keys.html

Robin Pokorny's Blog - medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

 

댓글