woolta

React 에서 배열컴포넌트 사용시 key 에 배열의 index는 가급적 사용하지 말자.!!

wooltaUserImgb00032 | React | 2021-03-21

React 에서의 key

key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도와줍니다. key는 엘리먼트 or 컴포넌트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 합니다. 이때 key 속성에는 해당 배열 내부에 각기 고유(uniquq) 값을 넣어 주어야 합니다.

list key 렌더링 예시

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

key를 지정해야 하는 이유

예를들어 3개의 리스트를 가진 변수를 통해 key가 없이 배열 랜더링을 진행하게 한다면 해당 리스트변수에 1개가 더 추가되는 경우라도 React 는 총 4개를 처음부터 다시 리렌더링 하게 됩니다. 하지만 key 를 지정한다면 기존의 요소들은 변경되지 않았다는걸 React 에서 자동으로 파악 후 새로생기는 요소에 대해서만 리렌더링을 진행하게 됩니다. 단순히 key 요소만 추가한것만으로도 더욱 최적화 된 랜더링을 진행할수 있습니다. :)

map 속성에서의 index를 통한 키 주입

배열을 통한 컴포넌트를 만들어야 하는 경우 해당 object 에 고유값이 무엇인지 모를 경우 map의 두번째 인자인 index 속성을 통하여 다음과 같이 key의 고유값을 지정하곤 합니다.

const arrayData = [{...},{...}];
const items = arrayData.map((obj, index) =>
  <li key={index}>
    {obj.name}
  </li>
);

해당 예시대로 작성할 경우 단순한 리스트 렌더링 역활만 한다면 문제가 생기지 않지만 정렬, 추가, 삭제 등의 작업이 있는 경우 예상치 못한 문제가 발생할 수 있습니다.

배열의 index 를 통한 키주입시 생기는 문제

우선 해당 문제를 파악하기 위해 우선 관련 예시코드를 작성해 보도록 하겠습니다.

예시 컴포넌트

사람들에 대한 리스트를 출력하도록 하고 이떄 map의 index 로 key를 잡도록 작성하였습니다. 또한 사람 추가 버튼을 클릭하면 추가된시람이 0번째 요소로 추가되도록 설정하였습니다.

import React, { useState } from 'react';

function PersonList() {
  const [personList, setPersonList] = useState([{ name: '성인'}, { name: '혜진' }]);

  // 사람 추가 이벤트 
  const addPerson = () => {
    setPersonList((prev) => [{ name: '추가된사람', description: '추가된 사람 입니다.' }, ...prev]);
  };

  return (
    <div>
      <h2>배열 index key 예시</h2>
      <button onClick={addPerson}>사람 추가</button>
      {personList.map((person, index) => {
        return (
          <div key={index}>
            <span>{person.name}</span>
            <input type='text' />
          </div>
        );
      })}
    </div>
  );
}

export default PersonList;

실행 화면

위의 컴포넌트 예시를 실행하면 다음과 같이 보이게 됩니다.

https://image.woolta.com/3fe0be51d4a78fee.png

이후 해당 컴포넌트의 input 영역에 다음과 같이 영역을 채워주도록 하겠습니다.

https://image.woolta.com/3fead4dd199a722c.png 이제 사람추가 버튼을 클릭하여 새로운 사람을 추가해준 후 결과를 보도록 하겠습니다.

https://image.woolta.com/3fde6c74f49d150a.png ??! 성인 영역에 있던 input 영역이 새로 추가한 쪽으로 이동하고 성인 쪽 영역에는 인풋이 초기화 되었습니다...

왜 이런 일이 발생하였는가?

사람추가 버튼을 누르게 되면 배열이 새로 바뀌게 되면서 컴포넌트가 리렌더링 이 되고 이때 index를 다시 매핑하게 됩니다. 이때 기존에는 index 0번이 성인 이였지만 다음번의 0번째 요소는 추가된 사람이 됩니다. React는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 위와 같이 예상치 못한 문제가 발생합니다.

리스트의 아이템 요소에 유니크 값이 없는경우 해결하기.

대부분의 경우는 각 배열 요소의 고유한 유니크 키 값이 존재할테지만 간혹 이렇지 못한 경우가 존재하기 마련입니다. 이런 경우는 nanoid 와 같은 uniqueID 생성 라이브러리를 통해 key 속성을 주입하면 됩니다.

nanoId 를 사용한 key 주입 예시

import React, { useState } from 'react';
import { nanoid } from 'nanoid';

function PersonList() {
  const [personList, setPersonList] = useState([{ name: '성인'}, { name: '혜진' }]);

  // 사람 추가 이벤트 
  const addPerson = () => {
    setPersonList((prev) => [{ name: '추가된사람', description: '추가된 사람 입니다.' }, ...prev]);
  };

  return (
    <div>
      <h2>배열 index key 예시</h2>
      <button onClick={addPerson}>사람 추가</button>
      {personList.map((person, index) => {
        return (
          // nanoid 를 사용한 고유 key 부여 
          <div key={nanoid()}>
            <span>{person.name}</span>
            <input type='text' />
          </div>
        );
      })}
    </div>
  );
}

export default PersonList;

index 요소는 그럼 반드시 사용하면 안되는 걸까요?

아닙니다.~ 물론 배열의 요소가 필터링, 정렬 삭제, 추가 등의 기능이 들어간다면 문제가 발생할수 있으나 다음과 같은 경우에서는 index로 사용해도 무방합니다. 다만 개인적으로는 코드의 일관성을 위해 최대한 index 를 사용 안하는 것을 추천드립니다. :)

  • 배열과 각 요소가 수정, 삭제, 추가 등의 기능이 없는 단순 렌더링만 담당하는 경우
  • id로 쓸만한 unique 값이 없을 경우
  • 정렬 혹은 필터 요소가 없어야 함

참고

Copyright © 2018 woolta.com

gommpo111@gmail.com