React에서는 동일한 컴포넌트 여러개를 렌더링 할 때, map 메서드를 이용한다.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
const posts = [
{ id: 1, title: "Hello World", content: "Welcome to learning React!" },
{ id: 2, title: "Installation", content: "You can install React via npm." },
{ id: 3, title: "Practice", content: "Practice React via npm run start" }
];
중괄호 사용시 return을 명시해 주어야 리턴이 된다.
export default function Blog() {
return posts.map((item) => {
return <div key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
})
}
괄호 사용시 return 키워드를 사용하지 않고 ()로 묶어줄 수 있다.
export default function Blog() {
return posts.map((item) =>
(<div key={item.id}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
))
}
key 속성을 지정하지 않은 기존 데이터 목록에 새로운 데이터를 추가하면 오른쪽 개발자도구에서 <div>
태그 하나가 추가되고 맨아래의 <div>
태그가 깜빡이는 걸 볼 수 있다.
겉으로 보면 새로운 데이터 한 개만 추가되니 해당되는 요소(<div>
) 한 개만 깜빡이는 게 맞는 것 처럼 보일 수도 있다.
(사실은 목록 중 맨 위에 새로운 데이터가 추가되는데 맨 아래의 <div>
태그가 깜빡이는 게 이상하기도 하다...)
아래의 사진처럼 모든 <div>
를 펼쳐보면 모든 <div>
태그 안에 있는 자식 요소들이 영향을 받고 있다.
즉 실제로는 새롭게 추가되는 데이터와 관련된 모든 요소들이(형제 요소들) 영향을 받고 있다.
(key속성 없이) map함수를 이용해 새로운 데이터를 추가할 때 리액트는 아래처럼 해석한다.
key 속성이 없다면 리액트는 새로운 아이템을 어디에 추가해야할 지 모른다. (아이템들을 서로 구분할 수 없다.)
그래서 리액트는 새로운 데이터를 담기 위한 <div>
태그를 기존 목록 중 맨 아래에 추가한다.
모든
아이템들의 내용을 업데이트 하여 내용(컨텐츠)를 교체한다.
위의 과정을 통해 최종적인 데이터를 보여준다.
즉 결과적으로는 관련된 모든 데이터를 체크하고 업데이트하여 렌더링한다.
이는 성능적으로 비효율적이다.
Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕습니다. (공식문서)
React에서 map을 이용해서 엘리먼트들을 표시하기 위해선 각 엘리먼트를 구분하기 위해 key 속성을 꼭 넣어주어야 한다.
key 속성의 위치는 map 메서드 내부에 있는 엘리먼트 즉, 첫 엘리먼트에 넣어야 한다.(가장 바깥에서 감싸고 있는 엘리먼트)
key 속성값은 가능하면 데이터에서 제공하는 id를 할당해야 한다.
key 속성값은 변하지 않고, 예상 가능하며, 유일해야 한다.
마땅히 고유한 id가 없는 경우에는 배열 인덱스를 넣어서 해결할 수 있다. (권장XXXX)
하지만 배열 인덱스는 최후의 수단(as a last resort)으로만 사용해야 한다.
Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다.
이처럼 key 속성을 지정하면 리액트는 배열의 길이 뿐만아니라 아이템을 식별할 수 있고
아이템이 어디에 위치해야 하는지를 알 수 있게 된다.
따라서 필요한 부분만 업데이트하여 새로운 데이터를 추가할 수 있다.
키는 리액트가 DOM 요소를 식별하기 위한 유일한 방법이다.
이러한 Key를 가지고 React는 어떤 항목을 변경, 추가 또는 삭제할지 식별한다.
그리고 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다.
그리고 항목들이 재배열되지 않는다면 이 방법도 잘 동작할 것이지만,
만약, 인덱스를 key로 사용 중 배열이 재배열되면 컴포넌트의 state와 관련된 문제가 발생할 수 있다.
컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용된다.
인덱스를 key로 사용하면, 항목의 순서가 바뀌었을 때 key 또한 바뀐다.
그 결과로, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수도 있다.
따라서 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다.
리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용한다.
이상적으로 키 값은 아이템이 생겨날 때 만들어지는 유일한 값을 사용하는 것이 좋다.
만약 적당한 키가 없다면 nanoid를 사용하는 것도 좋은 방법이다.
import { nanoid } from 'nanoid'
model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"
nanoid is a tiny, secure, URL-friendly, unique string ID, non-sequential generator for JavaScript.
Key는 배열 안의 형제 사이에서 고유해야 하지만, 전체 범위에서 고유할 필요는 없다.
두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'InstallatioKey는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없습니다. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있습니다.n', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
또한 React에서 key는 컴포넌트로 전달하지 않는다.
컴포넌트에서 key와 동일한 값이 필요하면 다른 이름의 prop으로 명시적으로 전달한다.
아래 예시에서 Post 컴포넌트는 props.id를 읽을 수 있지만 props.key는 읽을 수 없다.
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);