본문으로 바로가기

ko.reactjs.org/docs/lists-and-keys.html

 

리스트와 Key – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

list를 component로 뿌리기 위해서 다음과 같은 작업을 진행한 적이 있을 것이다.

사실 아래와 같은 방법은 안티 패턴이다. 반복되는 Component에 고유한 식별자인 key를 넣어주는 것이다.

추가적으로, 컴포넌트가 많아질때면 props를 기억하기 위해 React.memo를 이용하는 방법까지 살펴보았을 것이다.

[1, 2, 3].map((el, i) => <div key={i}>{el}</div>)

 

 

key를 이용한 force rendering

 

조금 변칙적인 사용법으로, 본래 사용법은 아니지만 force update를 위해서 key를 사용할 수 있다.
list가 아니지만 key로 state를 넣어두면, state가 바뀔때 force rendering됩니다.

<S.CardWrapper key={cardPage}>
  <div className="card-wrapper-inner">
    <div className="big-card">
      <SpringCard imgPath={`/springCard/spring${cardPage}-1.jpg`} maxWidth={35} maxHeight={35} minLimit={250} pathDirection="left" />
      <div className="small-card">
        <SpringCard imgPath={`/springCard/spring${cardPage}-2.jpg`} maxWidth={15} maxHeight={15} minLimit={125} pathDirection="right" />
      </div>
    </div>
  </div>
</S.CardWrapper>

 

 

Index as a key is an anti-pattern

 

공식문서에 따르면 key는 "React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕습니다. key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 합니다." 라고 합니다.

 

렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있습니다만! 공식문서에서는 권장하고 있지 않습니다.

 

아래와 같은 방식으로 사용하지 말라는거죠.

// 안티패턴
todos.map((todo, index) => (
    <Todo {...todo} key={index} />
  ));
}

 

 

왜냐고요? 

It may break your application and display wrong data!
왜냐하면 앱을 망가뜨리고 잘못된 데이터를 보여줄 수 있기 때문입니다.
출처 : robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318

 

key는 DOM을 식별하기 위한 유일한 도구인데, 연속된 값인 index를 넣게 된다면 중간에 무언가를 추가하거나 삭제하는 동작을 취할 경우에 문제를 일으킬 수 있습니다. 만약 새로 들어온 DOM의 key가 전과 같다면 바뀐 DOM이 아닌 과거의 DOM을 보여줄 수 있기 때문입니다. 

 

egghead라고 유명한 인터넷 코딩 강의 사이트에서도 이 문제로 골머리를 썩였나봅니다.

 

물론 항상 문제를 일으키는 것은 아닙니다.

아래와 같은 경우에는 인덱스를 key로 넘겨도 좋습니다.

 

  1. the list and items are static–they are not computed and do not change;
  2. the items in the list have no ids;  => nanoid나 date값에 random값을 붙이는 게 더 좋습니다.
  3. the list is never reordered or filtered.

 

 

위와 같은 예외가 아니라면 아래처럼 data에 자체적인 식별자가 있다면 이용하는 것이 좋습니다.

{
  todos.map((todo) => (
    <Todo {...todo} key={todo.id} />
  ));
}

 

식별자가 없다면 외부 패키지를 이용해서 id를 만들어내면 됩니다. nanoid도 있고, Date() + random 값을 매길수도 있습니다. 편하신 걸로 사용합시다.

import { nanoid } from 'nanoid';
const createNewTodo = (text) => ({
  completed: false,
  id: nanoid(),
  text
}

 

 

key는 왜 필요한 것인가?

ko.reactjs.org/docs/reconciliation.html#recursing-on-children

 

재조정 (Reconciliation) – React

A JavaScript library for building user interfaces

ko.reactjs.org

Viertual DOM이 동작하면서, 전의 dom 트리와 후의 dom 트리를 처리하는 과정 중 

DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.

 

그런데 아래와 같은 자식 dom을 전부 다 돌아야 한다면, <li> 가 많아질수록 비효율적이게 전부 순회를 해야할 것입니다.

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

 

이런 비효율을 개선하기 위해 식별자인 key를 매기는 겁니다.

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체