map 함수 적용시 key props를 부여하는 이유

younghyun·2022년 2월 24일
0
post-thumbnail

map()메서드

배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환.
리액트에서 동적인 배열을 렌더링해야 할 때는 이 함수를 사용하여 일반 데이터 배열을 리액트 엘리먼트로 이루어진 배열로 변환 해주면 됨.
배열이 고정적이라면 상관없겟지만, 배열의 인덱스를 하나하나 조회해가면서 렌더링하는 방법은 동적인 배열을 렌더링하지 못함. 동적인 배열을 렌더링해야 할 때에는 자바스크립트 배열의 내장함수 map() 을 사용해야 함.

//UserList.js
import React from 'react';

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}

function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com'
    }
  ];

  return (
    <div>
      {users.map(user => (
        <User user={user} />
      ))}
    </div>
  );
}

export default UserList;


console창에 에러가 나옴. 리액트에서 배열을 렌더링 할 때에는 key라는 props를 설정해야 함. key값은 각 원소들마다 가지고 있는 고유값으로 설정을 해야 함. 지금의 경우엔 id 가 고유 값.

import React from 'react';

function User({ user }) {
  return (
    <div>
      <b>{user.username}</b> <span>({user.email})</span>
    </div>
  );
}

function UserList() {
  const users = [
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
    {
      id: 2,
      username: 'tester',
      email: 'tester@example.com'
    },
    {
      id: 3,
      username: 'liz',
      email: 'liz@example.com'
    }
  ];

  return (
    <div>
      {users.map(user => (
        <User user={user} key={user.id} />
      ))}
    </div>
  );
}

export default UserList;

만약 배열 안의 원소가 가지고 있는 고유한 값이 없다면 map() 함수를 사용 할 때 설정하는 콜백함수의 두번째 파라미터 index 를 key 로 사용.

<div>
  {users.map((user, index) => (
    <User user={user} key={index} />
  ))}
</div>

배열을 렌더링 할 때 key 설정을 하지 않게 된다면 기본적으로 배열의 index 값을 key 로 사용하게 되고, 아까 봤었던 경고메시지가 뜨게됨. 이렇게 경고 메시지가 뜨는 이유는, 각 고유 원소에 key 가 있어야만 배열이 업데이트 될 때 효율적으로 렌더링 될 수 있기 때문.

Key

React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도움. key가 있으면 react는 배열 전체를 리렌더링 하지 않고, 배열에 추가된 요소 한가지만 다시 리렌더링 함. 변경이 없는 데이터까지 Dom을 조작해서 불필요한 자원을 낭비하지 않겠다는 것.

key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 함.

Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것. 대부분의 경우 데이터의 ID를 key로 사용함

렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있음.

항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않음. 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있음. 리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용.

key는 형제 사이에서만 고유한 값이어야 함.

key는 배열 안에서 형제 사이에서 고유해야 하고, 전체 범위에서 고유할 필요는 없음. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있음.

key 의 존재유무에 따른 업데이트 방식

예를 들어서 다음과 같은 배열이 있다고 가정

const array = ['a', 'b', 'c', 'd'];

위 배열을 다음과 같이 렌더링한다고 가정

array.map(item => <div>{item}</div>);

위 배열의 b 와 c 사이에 z 를 삽입하게 된다면, 리렌더링을 하게 될 때 <div>b</div><div>c</div> 사이에 새 div 태그를 삽입을 하게 되는 것이 아니라, 기존의 c 가 z 로바뀌고, d 는 c 로 바뀌고, 맨 마지막에 d 가 새로 삽입.

그 다음에 a 를 제거하게 된다면, 기존의 a 가 b 로 바뀌고, b 는 z 로 바뀌고, z는 c로 바뀌고, c는 d 로바뀌고, 맨 마지막에 있는 d 가 제거됨.

이러한 작업을 비효율적이며 key 를 이용해 개선할 수 있음.
객체에 다음과 같이 key 로 사용할 수 있는 고유 값이 있고

[
  {
    id: 0,
    text: 'a'
  },
  {
    id: 1,
    text: 'b'
  },
  {
    id: 2,
    text: 'c'
  },
  {
    id: 3,
    text: 'd'
  }
];

다음과 같이 렌더링 된다면
array.map(item => <div key={item.id}>{item.text}</div>);

배열이 업데이트 될 때 key 가 없을 때 처럼 비효율적으로 업데이트 하는 것이 아니라, 수정되지 않는 기존의 값은 그대로 두고 원하는 곳에 내용을 삽입하거나 삭제함.

때문에, 배열을 렌더링 할 때에는 고유한 key 값이 있는 것이 중요하며, 만약에 배열안에 중복되는 key 가 있을 경우 렌더링시에 오류메시지가 콘솔에 나타나게 되며, 업데이트가 제대로 이루어지지 않게 됨.

key 의 존재유무에 따른 업데이트 방식 2


  • 요소가 추가되었을 때

다시 한번 이 배열에서, 맨 앞쪽에 아래와 같은 요소가 하나 추가되었다고 해보자.

{id:4, title:"add!", content:"yeah!"}

그리고 나서 아까 보았던 두 번째 사진과 같은 map함수를 돌리고, 랜더링을 하려고하면, 리액트는 기존에 랜더링 했던 배열 요소와 새로운 배열 요소를 비교하게 됨. 예를 들어 이전에 key값이 0이었던 post의 내용과 현재 key값이 0인 post의 값을 비교하는데, 이 예제에서는 key:0인 배열에는 변화가 없음. 따라서 변화로 간주하지 않음.

key:1, key:2, key:3 역시 마찬가지인데, 배열에 새로운 key값인 4에 해당하는 배열은 기존에 없었음. 따라서 key:4인 배열은 새로 추가된 것으로 간주하고 이것만 랜더링 시켜줌.

  • 요소가 변경되었을 때

리액트는 아까와 동일한 방법으로 변경된 요소만 캐치해서 리렌더링함(map을 돌면서 key값을 post.id로 설정한 것을 잊지 말자)
이미 랜더링 했던 배열 요소와 새로 랜더링 할 배열의 요소를 비교함. 기존에 key:0 이었던 요소의 내용을 보니, 새로 받은 key:0인 배열과 비교했을 때 title과 content가 바뀐것을 알 수 있음. 그렇다면 key:0인 요소는 바뀐것. 이 부분은 리랜더링의 대상이 됨.
나머지 key:1, key:2, key:3은 변경이 없음. 리랜더링 하지 않을 것.

  • key를 map의 index로 하면 안되는 이유

그렇다면 배열을 element화 시키기 위해 map을 사용할 경우, key값을 index로 사용하면 안되는 이유를 알 수 있을 것.
배열의 첫 번째 위치에 새로운 값을 넣었다고 쳐보자.

{id:4, title:"add!", content:"yeah!"}

이 값을 배열의 맨 앞에 넣어다고 생각하면 될 것 같음. 그리고 나서 map을 돌릴 때, key를 단순히 index값을 주게 된다면 post.id는 무시된채 아래와 같은 상황이 될 것.

key: 0,  {id:4, title: 'add!', content:'yeah!'},
key: 1,  {id:0, title: 'hello!', content:'word'},
key: 2,  {id:1, title: 'myname!', content:'is!'},
key: 3,  {id:2, title: 'lee!', content:'yong!'},
key: 4,  {id:3, title: 'jun!', content:'blabla!'}

결국 리액트가 판단했을 때, 기존의 key와 value 매칭 쌍이 싹다 바뀐것. 기존에는 key:0인 것이 {id:0, title: ‘hello!’, content:’word’} 였는데, 새로 들어온 요소가 key:0이 되고, 기존의 key:0은 key:1이 되었으니 결과적으로 배열 전체가 완전히 바뀐것이라고 판단 할 수 밖에 없음. 이래선 리액트의 장점을 사용하지 못한 것

즉, state로 배열을 관리 한다면 (setState에 들어오는 배열 변동시 렌더링),
key=id를 넣게 되면 변화하는 요소만 삽입/삭제 렌더링 ( index처럼 밀려서 새로운 배열이 되는 게 아님 ). 새로 대에터 삽입될 때 그 부분만 캐치함.
key=index를 하게 되면 전체 렌더링 ( 위에 경우 처럼 밀려서 새로운 배열이 됨), 변경이 없는 데이터까지 Dom을 조작해서 불필요한 자원을 낭비하게 됨. index를 사용한다면 배열의 처음이나 중간에 새로 데이터가 삽입될 시 그 부분만을 캐치하지 못함.삭제될 때도 마찬가지!

출처
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://velog.io/@hyunn/%EB%B0%B0%EC%97%B4%EC%9D%98-map%EB%A9%94%EC%84%9C%EB%93%9C-key%EC%9D%98-%EC%9C%A0%EB%AC%B4
https://react.vlpt.us/basic/11-render-array.html
https://wooooooak.github.io/frontend/2019/01/30/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%B0%B0%EC%97%B4%EB%B3%80%EA%B2%BD/

profile
선명한 기억보다 흐릿한 메모

0개의 댓글