React에서 Key의 역할: UUID의 사용

Ji-Heon Park·2023년 4월 10일
1

Toy-Projects

목록 보기
1/1
프로젝트 이름프로젝트 개요진행날짜소스코드
Survey Forms토이 프로젝트, 설문조사 폼2023.04-2023.04private

배경

설문 조사폼을 구현하는 기업과제 수행 중 발생한 문제입니다. 리액트의 배열을 map()함수로 전개할 경우 각 요소마다 고유 key값을 주어야 합니다. 해당 프로젝트에서 전개하는 배열의 형태는 다음과 같습니다.

{
	question: '',
    answerType: '',
    ...
}[]

위와 같은 질문 배열을 아래와 같이 화면에 보여줍니다. 질문을 추가할때 '제목없는 질문' 이라는 default value가 있을 뿐 이를 식별할 방법이 없었습니다. 마땅히 부여할 key값이 없기에 index를 사용하였습니다.

문제

문제는 삭제 기능을 구현 후 나타났습니다. 데이터를 삭제시 실제 데이터에는 선택한 데이터 삭제가 잘 이루어지나 화면에는 엉뚱한 데이터가 삭제되어 화면에 보여졌습니다.

e.g. [0, 1, 2] 데이터 중 1번 데이터 삭제 -> 실제데이터 [0, 2] / 화면데이터 [0, 1]

원인

해당프로젝트에서는 CRUD, 드래그앤드랍 순서변경, Copy 등 데이터 엘리먼트의 변화가 많습니다. 이럴경우 index값을 key값으로 주기에는 적합하지 않습니다.

key의 존재 이유?

keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity". - 공식문서

공식문서에 의하면 Key는 그 값이 변하지 않는, 유일한 식별자의 역할을 가집니다. 즉, key 는 엘리먼트의 변화를 감지합니다. 브라우저가 변화를 감지하여 DOM 이 변경되는 과정을 간략하게 살펴봅시다.

  1. virtualDOM 에서 변화를 감지합니다
  2. 변화가 일어난 virtualDOM 과 realDOM 을 비교하여 새로운 DOM을 연산합니다.
  3. 비교 이후, realDOM 은 virtualDOM 과 동일하게 업데이트 됩니다.

realDOM은 모든 부분을 업데이는 하는 것이 아닌 변화가 감지된 부분만 업데이트 합니다. 아래 예시와 같이 key가 없는 경우, list 의 변화가 생길 시 리액트는 list 의 전체 순서가 변한 것으로 인지하여 모든 li 태그를 리렌더링 하게 됩니다. 즉 DOM을 비효율적으로 운영하게 됩니다.

<ul>
    <li> Svelte </li> 
    <li> React </li>
    <li> Vue </li>
</ul>

key가 없을 때는 리스트를 순차적으로 비교하며 변화를 감지합니다. 하지만 key가 존재할 땐, 리액트는 original DOM 과 비교하여 어떤 요소만 변화가 있는지 빠르게 찾아낼 수 있습니다. 즉, 이전처럼 모든 리스트를 순회할 필요 없이, 새롭게 추가된 요소만 업데이트 시킬 수 있습니다. key 를 사용하면 기존의 요소는 리렌더링하지 않고 변화가 감지된 요소만 리렌더링하여 효율적인 DOM 사용이 가능합니다. 결국 React 에서 key를 사용하는 이유는 엘리먼트 혹은 컴포넌트의 변화를 감지하기 위함입니다. 이는 효율적으로 DOM을 사용할 수 있습니다.

해결

엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 고유 ID를 지정해야 합니다. 이를 위해 react-uuid 라이브러리를 사용하였습니다. 엘리먼트에 id키를 만들어 uuid값을 value로 사용하였습니다. 아래 코드와 같이 폼 생성시 uuid로 생성된 고유값이 id값이 됩니다. 이후 map()의 키값으로 이 값을 사용합니다.

import uuid from 'react-uuid';
  
// ...생략
  
const FormCreate = () => {
// ...생략
  
 return <Button
        // ...
        onClick={() => {
          dispatch(addForm({ id: uuid() }));
          setFocusedIndex(forms.length);
        }}
      />
}

결과물

추가 조사: UUID ?

범용고유식별자(Universally Unique IDentifier)는 네트워크 상에서 고유성이 보장되는 id를 만들기 위한 표준 규약입니다. 즉, UUID는 식별자의 고유성을 보장하는 여러 구성요소로 구성된 특정 형식의 고유한 문자열을 만드는 데 사용되는 알고리즘입니다. UUID 표준에 따라 이름을 부여하면 고유성을 완벽하게 보장할 수는 없지만 실제 사용상에서 중복될 가능성이 거의 없다고 인정되기 때문에 많이 사용되고 있습니다.

UUID의 정의

16바이트(128비트) 길이의 숫자입니다. 이 숫자는 32개의 16진수로 구성되며, 5개의 그룹으로 표시되고 각 그룹은 하이픈으로 구분됩니다.

UUID는 버전에 따라 생성하는 규칙이 다른데, 버전 5까지 있습니다.

  • ver 1 (MAC 주소)
  • ver 2 (DCE 보안)
  • ver 3 (MD5 해시)
  • ver 4 (랜덤)
  • ver 5 (SHA-1 해시)

javascript에서 UUID 생성 방법

위 프로젝트에서는 라이브러리를 사용해 고유값을 생성하였지만 직접 구현하는 방법을 알아보았습니다.
MDN의 문서에 따르면 Math.random()은 암호학적으로 안전한 random number를 생성하지 않기 때문에 보안과 관련된 로직에서는 Math.random()을 사용하지 않는 것이 좋다고 합니다. 이러한 문제 때문에 W3C는 Web Crypto API를 만들어 공개했습니다.

const uuidv4 = () => {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );
}

console.log(uuidv4());

let uuid = self.crypto.randomUUID();
console.log(uuid);

Reference

profile
Frontend Developer | 기록되지 않은 것은 기억되지 않는다

0개의 댓글