리액트를 만지다 보니 state
에 key
값을 사용할 때 index 값을 사용하면 위험하다!⚠️ 라는 것은 들어본 적이 있다. 이유는 모른 채 그냥 Date.now()
메소드 만 썼는데..왜 일까?
리액트를 사용하다보면, state
로 배열을 관리해야 할 경우가 상당히 많다. 예를 들어 서버로부터 게시글 목록을 받아온다면, 아래와 같은 배열 리스트를 받아서 state
로 저장할 수도 있다.
배열의 각 요소마다 고유한 key
값을 지정해 주는 속성인데 만약 key
값을 생략한다면 어떻게 될까?
아마 이상없이 랜더링은 되겠지만, React는 아래와 같은 경고를 띄운다.
Each child in an array should have a unique “key” prop.
요소의 변화를 알아차리기 위해 필요하다.
리액트 공식문서를 보면, key
는 어떤 아이템이 변화되거나, 추가, 삭제되었는지를 알아차리기 위해 필요하다고 말한다.
리액트는 state
에서 변경사항이 있는 부분만 캐치해서 리랜더링 해준다. 리액트 유저라면 알고겠지만, 굳이 변경이 없는 데이터까지 Dom을 조작해서 불필요한 자원을 낭비하지 않겠다는 것이다.
배열에 고유한
key
값을 넘겨주었을 시에는state
의 배열에 어떤 요소를 추가해도 react는 배열 전체를 리랜더링 하지 않는다. 배열에 추가된 요소 한가지만 다시 리랜더링한다.
간단하게 한줄로 요약해보자면 원하지 않는 데이터가 보여질 수 있기 때문이다.
배열의 첫 번째 위치에 새로운 값을 넣었다고 쳐보자.
{id:4, title:"ah!", content:"yeah!"}
이 값을 배열의 맨 앞에 넣어다고 생각하면 될 것 같다. 그리고 나서 map을 돌릴 때, key
값을 단순히 index
로 주게 된다면 post.id는 무시된채 아래와 같은 상황이 될 것이다.
key: 0, {id:4, title: 'ah!', content:'yeah!'},
key: 1, {id:0, title: 'hello!', content:'word'},
key: 2, {id:1, title: 'myname!', content:'is!'},
결국 리액트가 판단했을 때, 배열 전체를 리랜더링 하면서 기존의 정렬이 전부 다 바뀌게 된다. 기존에는 key:0
인 것이 {id:0, title: ‘hello!’, content:’word’}
였는데, 새로 들어온 요소가 key:0
이 되고, 기존의 key:0
은 key:1이
되었으니 결과적으로 key
값을 index
로 사용한다면 배열의 처음이나 중간에 새로 데이터가 삽입, 삭제시 그 부분만을 캐치하지 못한다. 그렇기 때문에 eslint에서도 에러가 나는 것이다. index를 key로 하는것은 지양해야한다.
배열의 index를 key로 사용하면 정말 안되는 것일까?
Robin Pokorny 말에 의하면 아래 3가지를 만족 할 경우 안심하고 index를 key로 써도 된다고 이야기 한다.
- 배열과 각 요소가 수정, 삭제, 추가 등의 기능이 없는 단순 렌더링만 담당하는 경우
- id로 쓸만한 unique 값이 없을 경우
- 정렬 혹은 필터 요소가 없어야 함
리액트는 상태가 바뀌면 이전 상태의 트리와 이후 상태의 트리를 비교한다. 이 때 key
가 같다면 동일한 요소로 판단하고 업데이트 해준다.
배열의 순서나 내용이 변경되지 않는다면 아무 key
나 사용해도 된다(다르다는 전제 하에). 하지만 순서나 내용이 지속적으로 변화되는 상황이라면 개별 요소를 특정할 수 있는 key
를 사용해야 한다. 그렇지 않으면 매번 렌더가 발생하고 효율이 크게 떨어지기 때문이다!
항상 듣는 것이지만 유지보수에 좋은 코드를 만들자!
https://ko.reactjs.org/docs/lists-and-keys.html#gatsby-focus-wrapper
https://www.youtube.com/watch?v=ghxHAy3LH28
https://velog.io/@vlfflq2004/React-Index%EB%A5%BC-key%EB%A1%9C-%EC%93%B0%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0
https://ko.reactjs.org/docs/reconciliation.html#recursing-on-children