[React] key문제 원리/해결

eastZoo·2024년 5월 21일

React

목록 보기
12/15

0️⃣ 들어가며, 오늘의 문제

🐸 : 리액트에서 API로 받아온 json데이터나 여러 리스트(배열 안의 객체 등..) 데이터들을 표현하기위해 Map 함수를 사용하여 컴포넌트 혹은 JSX요소들을 랜더링 하다보면 다음과 같이 "key" prop ~ 형태의 Warning 메세지를 볼 수 있을 거에요.
에러가 떠도 렌더링이 잘되긴하는데 에러가 거슬리셨다고요??

오늘은 위의 key 에러가 발생하는 이유와 해결방법을 예시코드를 보면서 알아봐요



1️⃣ 예시코드로 한번에 이해하기

  • 파일구조 🗂️
📦src
 ┣ 📜App.css
 ┣ 📜App.jsx
 ┣ 📜index.css
 ┗ 📜index.jsx

🐸 : index.jsx는 기본 코드 세팅 그대로 입니다

  • App.jsx
// App.js
// App.js
import React from "react";
import "./App.css";

const items = [
  {
    id: 1,
    name: "lee",
    live: "Busan",
  },
  {
    id: 3,
    name: "Shin",
    live: "Seoul",
  },
  {
    id: 4,
    name: "Park",
    live: "Ulsan",
  },
  {
    id: 5,
    name: "Bin",
    live: "Daegu",
  },
];

function App() {
  return (
    <div style={{ textAlign: "center" }}>
      {items.map((item) => (
        <div>
          <button>{item.name}</button>
          <p>{item.live}</p>
        </div>
      ))}
    </div>
  );
}

export default App;

🐸 : 위의 코드 대로 해당 소스 코드 입력 후 localhost:3000에 접속하면 개발자 도구(F12)에 이미지와 같은 오류가 뜨는 것을 확인할 수 있어요.

자 이제 이러한 문제가 발생하는 궁극적인 원인 을 알기전!!

배경지식 점검!!

🐸 : 우리는 React의 데이터 관리 특징중 하나로
리액트는 데이터의 상태를 🟠 메모리에 저장하고 있다가
🟡 기존과 바뀐부분만 렌더링하여 화면에 그려준다는 것을 알고 있을거에요.

  • 🟠 메모리에 저장 ( Virtual DOM )

    Virtual DOM: React는 실제 DOM 대신에 메모리
    가상의 DOM 트리를 유지합니다. 이 가상의 DOM 트리는 React 요소의 경량 복사본으로, 상태 변경 시 빠르게 조작하고 비교할 수 있도록 설계되었습니다.

    참고 : https://velog.io/@eastzoo/React-Virtual-DOM-동작-원리

  • 🟡 기존과 바뀐부분

    기존과 바뀐부분 을 탐지할때 여기서 map함수를 돌려 생성한 컴포넌트또는 html요소
    고유한 Key값이 없다면 모든 데이터를 비교해야 하지만, 유니크한 Key가 있으면 Key값만 비교하여 Key가 추가 됐는지, 삭제 됐는지만 비교하면 되기 때문에

렌더링을 줄여줌과 동시에 원하는 방식으로 동작할 수 있다는 이점이 있죠.

그렇기에 위와같이 중복되지 않는 고유한 값을 key 값으로 부여해 주길 리액트는 원하고
( 웹 실행에는 영향이 가진 않지만 에러를 나타내고 ) 있는 것이에요.




2️⃣ 보편적 해결법

해결법 #1

map함수의 index를 사용하여 key값을 넣어준다.

...
const items = [
  .. //위의 예시와 동일
  ]
...
..
function App() {
  return (
    <>
      {items.map((item , index) => (
        <div key={index}>
          <button>{item.name}</button>
          <p>{item.live}</p>
        </div>
      ))}
    </>
  )
}

export default App;

위와같이 key={index}라는 값을 넣어주면 에러가 해결되는 모습을 볼 수 있다. 요소들에 map함수로 부터 0~n번 값의

고유한 값들이 들어가기 때문이다.

🐸 : index 변수의 이름은 마음대로 지어주면된다
( idx, index, i 등등..)


💡 문제발생

하지만 key문제는 해결되는 반면 또 다른 문제가 발생한다. 문제를 명확히 보기위해서

위의 코드를 바꿔보았다.

import React, { useEffect, useState } from 'react'

const itemList = [
  [
    { id: 1, name: "lee",live: "Busan" },
    { id: 3, name: "Shin",live: "Seoul"},
    { id: 2, name: "Park",live: "Ulsan"},
    { id: 4, name: "Bin", live: "Daegu"}
  ],
  [
    { id: 4, name: "Bin", live: "Daegu"},
    { id: 1, name: "lee",live: "Busan" },
    { id: 2, name: "Park",live: "Ulsan"},
    { id: 3, name: "Shin",live: "Seoul"}
  ],
  [
    { id: 3, name: "Shin",live: "Seoul"},
    { id: 4, name: "Bin", live: "Daegu"},
    { id: 1, name: "lee",live: "Busan" },
    { id: 2, name: "Park",live: "Ulsan"}
  ],
  [
    { id: 2, name: "Park",live: "Ulsan"},
    { id: 3, name: "Shin",live: "Seoul"},
    { id: 4, name: "Bin", live: "Daegu"},
    { id: 1, name: "lee",live: "Busan" }
  ],
  
];

function App() {
  const [items, setItems] = useState(itemList[0]);

  console.log(items);
  
  //랜덤으로 자리 바꿔주는 함수, 위의 itemList에서 미리 자리를 바꿔놓고
  //랜덤함수로 무작위 함수를 뽑아내 setItem을 통해 1초마다 다른 배열을 선택한다.
  useEffect(() => {
    const interval = setInterval(() => {
    const random = Math.floor(Math.random() * 3);
    setItems(itemList[random]);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <>
      {items.map((item, index) => (
        <div key={index}>
          <button>{item.name}</button>
          <p>{item.live}</p>
        </div>
      ))}
    </>
  )
}

export default App;

랜덤으로 자리 바꿔주는 함수를 추가하여 itemList에서 미리 무작위로 자리를 바꿔놓은 리스트중

랜덤함수로 무작위 하나( itemList[n] ) 뽑아내 setItem을 통해 1초마다 다른 배열을 선택한다.

위의 코드를 실행시켜 두번째 버튼에 탭(Tab)키를 통해 타겟을 놓아보았다. ( 웹화면을 띄워놓고 탭키 두번 누르면된다 )

아래 그림과 같이 분명 처음 shin이라는 버튼에 타겟을 놓았지만 안의 요소가 바뀔때 shin을 따라가지 않고

타겟 자리는 그대로며 버튼안의 이름만 바뀌는 것을 볼 수 있다.

🐸 : 나는 shin이라는 값을 포커싱 하길 원했는데



⁉️ 이유는

map함수의 index로부터 얻는 key 값은 다른 값과 겹치지 않는 고유한 값이긴 하지만 리스트 안 객체들의 고유값이 아닌

map함수가 실행될때마다 ( 마치 map함수를 몇번 돌았다는 의미없는 map 함수가 뱉어내는 일련번호 ) 새로 번호를 붙여주는 것이기 때문에, 매번 첫번째 있는 요소들이 0번이되며

새롭게 넘버링이 되어 key갑의 의미가 사라진다.

idx로 key값을 준다면 안준 것과 매한가지이다 ,
단지 key 에러를 피할 뿐

이러한 문제를 해결하기위한 두번째 방법이 있다.



해결법 #2

객체에 들어있는 구별할 수 있는, 중복된 값이 존재하지 않는 id 값같은 특정 value 같은 값을 key로 주면된다.

import React, { useEffect, useState } from "react";

const itemList = [
 [
   { id: 1, name: "lee", live: "Busan" },
   { id: 3, name: "Shin", live: "Seoul" },
   { id: 2, name: "Park", live: "Ulsan" },
   { id: 4, name: "Bin", live: "Daegu" },
 ],
 [
   { id: 4, name: "Bin", live: "Daegu" },
   { id: 1, name: "lee", live: "Busan" },
   { id: 2, name: "Park", live: "Ulsan" },
   { id: 3, name: "Shin", live: "Seoul" },
 ],
 [
   { id: 3, name: "Shin", live: "Seoul" },
   { id: 4, name: "Bin", live: "Daegu" },
   { id: 1, name: "lee", live: "Busan" },
   { id: 2, name: "Park", live: "Ulsan" },
 ],
 [
   { id: 2, name: "Park", live: "Ulsan" },
   { id: 3, name: "Shin", live: "Seoul" },
   { id: 4, name: "Bin", live: "Daegu" },
   { id: 1, name: "lee", live: "Busan" },
 ],
];

function App() {
 const [items, setItems] = useState(itemList[0]);

 console.log(items);

 //랜덤으로 자리 바꿔주는 함수, 위의 itemList에서 미리 자리를 바꿔놓고
 //랜덤함수로 무작위 함수를 뽑아내 setItem을 통해 1초마다 다른 배열을 선택한다.
 useEffect(() => {
   const interval = setInterval(() => {
     const random = Math.floor(Math.random() * 3);
     setItems(itemList[random]);
   }, 1000);

   return () => {
     clearInterval(interval);
   };
 }, []);

 return (
   <>
     <>
       {items.map((item) => (
         <div key={item.id}>
           <button>{item.name}</button>
           <p>{item.live}</p>
         </div>
       ))}
     </>
   </>
 );

export default App;

위와같이 구별된 값인key값을( <div key={item.id}> ) 쥐어준다면 Tab키를 통해 타겟을 해놓은 요소가 자리가 바뀔때마다

타겟이 옮겨다니는 것을 볼 수 있다.

🐸 : item.id 값은 주어진 items 데이터를 보면
순서는 바뀌어도 name:"Shin" 인 값의 id 값은 항상 3인 것을 볼 수 있다,

이렇듯 api로 받아온 배열데이터 혹은 자신이 표현하고자 하는 배열 데이터속의 각 객체가 본인만 가질수있는
구별되는 고유한 값을 key값으로 지정해 준다면 리액트는 그 요소의 변경점과 현재 상태를 더 잘 파악할 수 있으며 효과적으로 렌더링 할 수 있다.

리액트가 key값을 통해 자리가 랜덤으로 바뀌어도 데이터 상태를 기억하고 렌더링 해주는 것이다.

리액트가 개별 아이템을 인식할 수 있도록 도와주는 것이 바로 key이다.!.!!

끝🙋🏻

💬 : 위 내용에대해 더 궁금한점이 있거나,
틀린내용이 있거나
질문이 있다면 댓글로 달아주세요.!

profile
Looking for an answer to what is a developer🧐;

0개의 댓글