[React] 컴포넌트의 key값을 고유하게 설정해야하는 이유

hoonsbory·2022년 12월 30일
0

리액트 컴포넌트의 key값을 고유하게 설정해야하는 이유

  • 리액트는 state가 변경되면 DOM을 다시 렌더링하는데, 이 때 key값을 이용하여 추가 삭제 여부를 결정한다.
  • 렌더링과는 개념이 다르다. 렌더링은 부모가 렌더링 되었기 때문에 자식이라면 무조건 렌더링이 될 수 밖에 없다.
  • 차이는 컴포넌트의 생성과 관련이 있는데, key값을 고유한 값이 아닌 index로 설정한다면, 순서가 바뀌는 리스트의 경우, key값이 뒤엉켜져 , 새로 추가해야될 컴포넌트가 변경될 수 있다.
import React, { useState } from 'react';
import UserItem from './UserItem';

function App() {
  console.log('UserList component render');

  const [users, setUsers] = useState([
    {
      id: 0,
      name: 'Kim',
      age: 27,
      score: 80,
    },
    {
      id: 1,
      name: 'Jo',
      age: 25,
      score: 70,
    },
  ]);

  const addUser = () => {
    setUsers([
      {
        id: 2,
        name: 'Jung',
        age: 30,
        score: 90,
      },
      ...users,
    ]);
  };

  return (
    <div>
      <button
        value="새 유저 생성"
        disabled={users.length >= 3}
        onClick={addUser}
      />
      {users.map((user,idx)=> {
        return <UserItem key={idx} user={user} />;
      })}
    </div>
  );
}

export default App;

버튼을 누르면 state에 새 객체를 unshift하는 코드다.

key값을 index로 설정하면 새롭게 add된 Jung이라는 유저는 unshift되어 0번째 인덱스를 가진다.

그리고 기존에 존재하던 0,1번째 인덱스의 유저들은 한 칸 씩 밀리게 되어 key값이 변경되게 된다.

이렇게 될 경우에 0,1이라는 key는 이미 존재했었고, 2라는 key가 추가됐기 때문에 리액트는 2라는 key를 가진 컴포넌트를 생성한다.

새롭게 추가된 컴포넌트는 0번째 객체지만, 리액트는 0번째 객체가 이미 존재하던 객체라고 생각하고 새롭게 추가된 key 2 값을 가진 컴포넌트만 생성한다. (기존 key값을 가진 컴포넌트는 렌더링만)

여기서 생기는 문제는, 새롭게 배열에 추가한 컴포넌트는 렌더링만되고 useEffect가 실행되지않는다. 기존에 있던 컴포넌트로 인식하여 렌더링만 해주기 때문이다. 결국 원하지 않은 마지막 인덱스 컴포넌트만 계속 재생성되며 useEffect가 실행된다.

key값은 생략하면 자동으로 idx로 세팅된다. 단순하게 push만 하는 로직에서는 idx로 설정해도 아무 상관이없다. 결국 마지막 index의 key값만 생성이 되기 때문에 로직에 영향을 끼치지 않는다.

그러나 위 예제처럼 idx의 순서가 바뀌게 되는 로직에서는 컴포넌트 생성에서 문제를 일으킬 수 있다.

Math.random()로 랜덤한 값을 설정해도 생성이 되지만 이 경우엔 ,

모든 컴포넌트의 key값이 다 바뀌어서 전부 재생성 되기 때문에 성능 문제를 초래한다.

위 로직은 useEffect에 중요 로직이 있는 경우 문제가 될 수 있지만 그렇지 않은 경우엔 큰 문제가 되지는 않는다. 어차피 렌더링은 되기 때문이다.

위 로직에서 유저가 추가될 때마다 부모가 렌더링 되지만 사실 UserItem은 리렌더링이 될 필요가 없다. (그냥 배열의 값을 출력만 하는 로직이기 때문)

이럴 경우는 memo를 통해 렌더링을 방지할 수 있다.

> React.memo 는 props가 바뀌지 않았을 때 렌더링을 방지해준다. 

위 로직은 UserItem에게 항상 같은 props를 넘겨주어 렌더링을 할 필요가 없으므로 memo를 사용하여 렌더링을 방지하면 성능 개선에 도움이 될 수 있다.

import React, { useEffect } from 'react';

function UserItem({ user }) {
  console.log(`UserItem (i d: ${user.id}) component render`);

  return (
    <div className="user-item">
      <div>이름: {user.name}</div>
      <div>나이: {user.age}</div>
      <div>점수: {user.score}</div>
    </div>
  );
}

export default React.memo(UserItem);

위와 같이 컴포넌트를 React.memo로 감싸주면 리렌더링이 방지된다.

그러나 여기서 확인해야할건, 아래와 같이 인덱스로 키값을 설정할 경우는 컴포넌트를 다시 생성하지는 않지만, 배열 순서가 바뀔 경우 props가 바뀌는 것이므로 React.memo를 써도 렌더링을 방지할 수 없다.

그래서 꼭 key값은 고유의 값을 넣어주는 게 좋다. (물론 순서가 바뀌지않는 배열은 idx로 해도 무방)

{users.map((user,idx)=> {
        return <UserItem key={idx} user={user} />;
      })}

0개의 댓글