리액트 state들의 리스트에 map()
메소드를 활용하여 여러 개의 컴포넌트들을 생성하는 것은 매우 흔한 일이다. 특히 이때 각 컴포넌트의 고유한 값으로 key
프로퍼티를 지정해 주는 것이 권장된다.
그러나 만약 컴포넌트마다 가지는 고유한 값이 마땅히 없다면, 랜덤한 값을 생성하여 key
로 넣어주는 방안을 떠올릴 수도 있다.
아래는 uuid
라이브러리의 v1()
함수를 호출하여 그 생성값을 key
로 사용하는 '댓글 컴포넌트'의 예시이다.
한편, 댓글 작성란에 텍스트를 입력하는 행동은 이미 등록된 다른 댓글들과는 상관이 없으므로 불필요한 리렌더링을 일으키지 않아야 한다.
위 컴포넌트의 props인 comment
, accessible
은 단순 텍스트 입력에 의해 바뀌지 않는 값이며, handleComment
역시 페이지 컴포넌트에서 먼저 useCallback
처리가 된 후 전달된다.
물론 댓글 컴포넌트 자체도 React.memo
로 감싸주었다. 즉, 예상대로라면 props가 바뀌지 않으므로 불필요한 리렌더링이 발생하지 않을 것이다.
하지만... 글자 하나를 입력할 때마다 리렌더링이 일어나는 것을 볼 수 있다. 각 댓글 컴포넌트의 편집/삭제 버튼과 프로필 이미지가 계속 깜빡이고 있다.
원인은 key
의 값으로 넣어준 v1()
이 매번 재실행되며 서로 다른 값을 반환하는데, 이것이 해당 컴포넌트 내부에서 props로 받아오는 값은 아니지만 렌더링에 영향을 미친다는 점을 간과한 것이다.
애초에 key는 리액트가 가상 DOM 트리를 비교할 때 참조하여 더욱 효율적인 리렌더링을 수행하기 위한 용도인데, 이것이 매번 새로운 값으로 변한다면 key를 쓰는 의미가 없어진다.
컴포넌트는 매번 비효율적으로 모든 비교를 거쳐 리렌더링될 것이고, 각 컴포넌트마다 uuid()를 호출하는 비용만 추가로 더해질 뿐이다.
랜덤값 생성 함수를 호출하는 대신, 고정된 값인 인덱스를 key로 사용하면 이러한 현상은 없어진다.
하지만 인덱스 역시 주의해야 할 점이 있는데, 순서가 바뀔 수 있는 컴포넌트 간에는 사용하면 안 된다는 것이다.
이 문제는 이미 예전에 겪어본 적이 있었다. 단순히 컴포넌트 간 순서를 바꾸기만 했는데도 똑같은 컴포넌트가 복사되는 등 비정상적인 동작이 일어나 당황했던 기억이 있다.
가장 좋은 방법은 역시 컴포넌트가 가진 고정된 고유한 값을 key로 사용하는 것이다. 스키마 정의 단계에서부터 id와 같은 식별자를 꼭 멤버로 포함시키도록 하자.
key
로 고유값 생성 함수를 써도 되는 경우: 값 생성에 드는 비용이 크지 않을 때, 리렌더링 여부가 중요하지 않을 때key
로 인덱스를 사용해도 되는 경우: 컴포넌트 간 순서가 바뀌거나 새로 추가/삭제될 일이 없고, 단순 렌더링을 위한 목적으로 쓰일 때