쾌락코딩

리액트 배열의 key 값 존재 이유

|

리액트를 사용하다보면, state로 배열을 관리해야 할 경우가 상당히 많다. 예를 들어 서버로부터 게시글 목록을 받아온다면, 아래와 같은 배열 리스트를 받아서 state로 저장할 수도 있다.

react_key1

그리고 난 후 이 배열을 이용해서, 알맞은 UI를 그리기 위해서 map함수를 사용해 element로 바꾸는 작업을 하곤 한다. 예를 들자면 아래와 같다.

react_key2

key 값은 뭐지?

mapping하는 부분을 보면 key 속성을 볼 수가 있다. 배열의 각 요소마다 고유한 key값을 지정해 주는 속성인데 만약 key값을 생략한다면 어떻게될까?

아무 이상없이 랜더링은 되지만, React는 아래와 같은 경고를 띄운다.

Each child in an array should have a unique “key” prop.

그럼 key값은 언제 필요한 것일까?

요소의 변화를 알아차리기 위해 필요하다

리액트 공식문서를 보면, key는 어떤 아이템이 변화되거나, 추가, 삭제되었는지를 알아차리기 위해 필요하다고 말한다.

리액트는 state에서 변경사항이 있는 부분만 캐치해서 리랜더링 해준다. 리액트 유저라면 알고겠지만, 굳이 변경이 없는 데이터까지 Dom을 조작해서 불필요한 자원을 낭비하지 않겠다는 것이다. 그렇다면 state의 배열에 어떠한 요소가 추가가 된다고 가정해보자. 배열에 어떤 요소를 추가했으니 배열이 변경된 것이라고 생각할 수 있는데, 과연 react는 배열 전체를 리랜더링 할까? 아니면 배열에 추가된 요소 한가지만 다시 리랜더링 할까?

리액트는 참 똑똑하게 배열에 추가된 딱 한가지 요소만 리랜더링한다. 다만, 배열의 key값을 고유하게 넘겨주었을 때만.

어떻게 그럴 수 있을까?

먼저 배열에 새로운 요소가 추가됬을 때를 알아보자.

요소가 추가되었을 때

react_key1 다시한번 이 배열에서, 맨 앞쪽에 아래와 같은 요소가 하나 추가되었다고 해보자.

{id:4, title:"add!", content:"yeah!"}

그리고 나서 아까 보았던 두 번째 사진과 같은 map함수를 돌리고, 랜더링을 하려고하면, 리액트는 기존에 랜더링 했던 배열 요소와 새로운 배열 요소를 비교하게 된다. 예를 들어 이전에 key값이 0이었던 post의 내용과 현재 key값이 0인 post의 값을 비교하는데, 이 예제에서는 key:0인 배열에는 변화가 없다. 따라서 변화로 간주하지 않는다.

key:1, key:2, key:3 역시 마찬가지인데, 배열에 새로운 key값인 4에 해당하는 배열은 기존에 없었다. 따라서 key:4인 배열은 새로 추가된 것으로 간주하고 이것만 랜더링 시켜준다.

요소가 변경되었을 때

이제 요소가 변경되었을 때를 알아보자. 위의 사진에서 첫 번째 요소가 아래와 같이 바꼈다고 가정해보자. react_key3

리액트는 아까와 동일한 방법으로 변경된 요소만 캐치해서 리랜더링한다(map을 돌면서 key값을 post.id로 설정한 것을 잊지 말자)

이미 랜더링 했던 배열 요소와 새로 랜더링 할 배열의 요소를 비교한다. 기존에 key:0 이었던 요소의 내용을 보니, 새로 받은 key:0인 배열과 비교했을 때 title과 content가 바뀐것을 알 수 있다. 그렇다면 key:0인 요소는 바뀐것이다. 이 부분은 리랜더링의 대상이 된다.

나머지 key:1, key:2, key:3은 변경이 없다. 리랜더링 하지 않을 것이다.

key를 map의 index로 하면 안되는 이유

그렇다면 배열을 element화 시키기 위해 map을 사용할 경우, key값을 index로 사용하면 안되는 이유를 알 수 있을 것이다.

배열의 첫 번째 위치에 새로운 값을 넣었다고 쳐보자.

{id:4, title:"add!", content:"yeah!"}

이 값을 배열의 맨 앞에 넣어다고 생각하면 될 것 같다. 그리고 나서 map을 돌릴 때, key를 단순히 index값을 주게 된다면 post.id는 무시된채 아래와 같은 상황이 될 것이다.

key: 0,  {id:4, title: 'add!', content:'yeah!'},
key: 1,  {id:0, title: 'hello!', content:'word'},
key: 2,  {id:1, title: 'myname!', content:'is!'},
key: 3,  {id:2, title: 'lee!', content:'yong!'},
key: 4,  {id:3, title: 'jun!', content:'blabla!'}

결국 리액트가 판단했을 때, 기존의 key와 value 매칭 쌍이 싹다 바뀐것이다. 기존에는 key:0인 것이 {id:0, title: ‘hello!’, content:’word’} 였는데, 새로 들어온 요소가 key:0이 되고, 기존의 key:0은 key:1이 되었으니 결과적으로 배열 전체가 완전히 바뀐것이라고 판단 할 수 밖에 없다. 이래선 리액트의 장점을 사용하지 못한 것이다.

결론

state로 배열을 관리한다면, map 사용시 key로 index를 사용하지 말자. key로 index를 사용한다면 배열의 처음이나 중간에 새로 데이터가 삽입될 시 그 부분만을 캐치하지 못한다. 삭제될 때도 마찬가지!

Comments