본문 바로가기

에러

[React] Warning: Each child in a list should have a unique "key" prop.

현재 나는 오픈소스 컨트리뷰톤 githru 팀에서 활동하고 있다.

컨트리뷰톤을 진행하면서 PR로써 주고 받은 내용들 + 나도 조금 더 생각해볼만한 주제도 많아서 쓸 수 있는 만큼 최대한 자료를 조사해서 내 스스로의 기준을 만들어보려고 한다.

 

 

nanoid 라이브러리를 추가하게 된 경위

key props 추가

내가 3주 동안 맡아서 구현한 파트는 Summary 부분인데 이 부분을 구현할 때 데이터를 가공해 map으로 span 태그를 뿌려주다보니 key없이 뿌려질 경우 콘솔에서 key prop을 전달해달라고 에러를 뿜는 것이었다.

Warning: Each child in a list should have a unique "key" prop.

 

 

그렇다면 key props를 왜 지정해줘야하는 것일까?

이 포스트에 따르면 만약 key 없이 배열 렌더링을 진행한다면 배열에 1개의 데이터가 더 추가되는 상황이라도 React는 총 N+1개의 요소를 처음부터 리렌더링한다고 한다. 그렇지만 key를 지정한다면 기존 요소가 바뀌지 않으면 그대로 두고 새로 생기는 요소만 리렌더링을 한다고 한다.

1. array index를 key로 넣는 것은 안될까?

이걸 어떻게 해결할까 고민을 하다가 array index로는 안될까? 해서 해보니까 이번에는 array index는 key에 못쓴다는 에러가 나왔다.

Do not use Array index in keys

자료를 찾아보니 React 공식 Document에서 하지말라고 한 내용도 있었다. 아무래도 이 부분 때문에 lint에서 걸린 것 같다.
React에서는 key가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕기 때문에 array의 index로 key를 설정할 경우 데이터를 동적으로 추가하고 삭제하는 과정에서 예상하지 못한 결과를 얻을 수 있기 때문에 그렇다고 한다. 자세한 내용은 여기서 확인할 수 있다.

2. 어떻게 하면 고유한 값을 만들 수 있을까?

당시 가지고 있는 데이터에서 커밋 아이디를 모아오는 ids 속성이 있었다. 그래서 이 친구를 랜덤으로 이용해봐야겠다고 생각해서 우선은 그런 방향으로 구현했다. (당연히 안티패턴이었다.) 오프라인 회의에서 해당 코드를 보신 팀원들께서 React 18의 useIdnanoid 라이브러리를 추천해주셨다.

  • React 18의 useId

React 18의 useId의 경우 다음과 같이 값이 주어진다.

import Head from 'next/head'
import styles from '../styles/Home.module.css'
import { useId } from 'react'
import Child from '../src/components/child'
import SubChild from '../src/components/SubChild'

export default function Home() {
  const id = useId()
  return (
    <>
      <div className="field">Home: {id}</div>
      <SubChild />
      <SubChild />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
      <Child />
    </>
  )
}
  1. 장점
    • 추가적으로 라이브러리를 설치하지 않아도 된다.
    • 바로 react에서 가져다 쓸 수 있다.
  1. 단점
    • 아이디의 값이 :r0: :r1: :r2: ... 와 같아서 진정으로 고유한가에 의문이 있다.
  2. 결론
    • 장점이 있지만 진정으로 고유한 값을 구해야하는 상황에서 사용해보니 고유의 값이 아니라는 에러가 떴다. 역시 지금의 내 상황에서 쓰는 것은 어려울 것 같다.

 

  • Symbol()

Example:

let id = Symbol("id");

Javascript에서 유일한 식별자를 만들고 싶을 때 사용한다. Symbol은 유일성이 보장되는 자료형이기 때문에 설명이 동일한 심볼을 여러 개 만들어도 각 심볼값은 다르다. 전달된 문자열은 디버깅 용도로만 사용된다.

  1. 장점
    • 추가적으로 라이브러리를 설치하지 않아도 된다.
    • 프레임워크 등에서 가져다 쓰는 게 아니라 javascript의 문법이라 바로 사용 가능하다.
  2. 단점
    • 디버깅 용도로만 사용할 수 있다.
    • 프로덕션에서는 어떻게 보이는지 어떤 에러를 뿜어낼지 정확히 알 수 없다.
  3. 결론
    • 한번 실험해볼 수 있을 것 같다. 실험 내용은 디버깅 때만 에러가 발생하지 않는 건지 같은 내용으로 실험해볼 예정.

 

  • uuid

특히 컴포넌트에 고유한 키를 요구하는 리액트로 개발할때 유용하게 사용할 수 있는데 이때 유용하게 사용할 수 있는 모듈을 소개한다.

Example:

import { v4 as uuidv4 } from 'uuid'; uuidv4();

uuid 라는 모듈로 UUID를 생성하는 함수를 제공하는 모듈이다. UUID는 Universally Unique IDentifier, 범용 고유 식별자의 약자로 고유한 아이디 값을 생성할때 사용할 수 있는 표준이다.

 

  • nanoid

Example:

  import { nanoid } from 'nanoid';

  // 기본 옵션으로 사용하는 방법
  const key = nanoid();
import { customAlphabet } from 'nanoid';

// 변수의 형식, 자릿수를 정해서 사용하는 방법
const nanoid = customAlphabet("01234567899abcedf", 6);
const key = nanoid();

nanoid도 고유한 값을 설정해주는 라이브러리이다. nanoid의 경우 특이한 점이 있는데 사용자가 임의로 어떤 값을, 몇 자리의 글자로 고유하게 만들 것인지 정할 수 있다는 점이다.

 

3. nanoid를 선택한 이유

우선 Math.random()으로 id를 잘라 고유하게 만들어서 사용하는 걸 보고 팀원분이 추천해주셨는데 우선 실험용으로 선택해보았다. 다른 방법이 있는지 이것을 설치할 당시에는 알아보지 않았다. 우선 구현이 먼저라고 생각했다. 후에 피드백을 받으면서 nanoid가 아니라 uuid도 있다는 것을 알게 되면서 nanoid 뿐 아니라 고유 아이디를 생성하는 방법을 좀 더 찾아보았다.

 

4. 값의 유일성이 보장되는 상황임에도 nanoid 라이브러리를 삭제하지 않는 이유

nanoid는 크기가 108byte에 불과하다. uuid와 달리 nanoid는 크기가 4.5배 작고 종속성이 없다. 또한 크기 제한을 사용하여 다른 35%에서 크기를 줄였다.

크기 축소는 데이터 크기에 직접적인 영향을 미친다. 예를 들어, Nanoid를 사용하는 개체는 데이터 전송 및 저장을 위해 작고 컴팩트하다. 애플리케이션이 증가함에 따라 이러한 숫자가 표시된다.

nanoid는 uuid에 비해 더 안전하다. 대 부분의 랜덤 생성기에서는 안전하지 않은 Math.random()을 사용한다. 그러나 nanoid는 보다 더 안전한 crypto module 및 Web Crypto API를 사용한다.

nanoid는 id 생성기를 구현하는 과정에서 random % alphabet을 사용하는 대신 균일 알고리즘이라는 자체 알고리즘을 사용한다.

nanoid는 uuid보다 60% 빠르다. uuid의 알파벳이 36자 사용되는 데 비해 nanoid는 21자만 사용한다.

그리고 uuid보다 nanoid의 스타 수가 더 많으며 가장 최근까지도 기여 활동이 이뤄지고 있는 프로젝트기 때문에 선택하게 되었다.

 

결론

새로운 라이브러리를 설치하는 것 자체가 큰 폼이니 최대한 새로운 라이브러리를 설치하는 것을 경계하며 어떻게 하면 기존 문법으로 해결할 수 있는가에 대해 고민해보는 태도가 필요하다.

uuid와 nanoid를 비교하면 nanoid가 낫지만 javascript나 React의 문법으로 해결할 수 있다면 그렇게 해결하는 것이 좋고, 가장 좋은 방법은 값의 유일성을 보장하게 데이터를 가공하는 것이다. 보장하지 못하는 경우가 된다면 nanoid나 기존 문법으로 해결할 수가 있겠다.

 

 

출처

Why is NanoID Replacing UUID?

심볼형 - 모던 Javascript 튜토리얼

nanoid usage in react

Githru Pull Request #63 Comment

 

반응형