해석하며 공부하는 것을 목적으로 하기 때문에 다수의 의역, 오역이 있음을 미리 밝힙니다.
순서로는 main concept 8번에 속하며, 순서대로 읽으면서 공부하고 있는 과정이 아니기 때문에 무작위로 해석할 예정입니다.
- 원본 : https://reactjs.org/docs/lists-and-keys.html
(reactjs.org앞에 ko.을 붙여주시면 리액트 공식문서에서 제공해주는 한국어 번역본을 볼 수 있습니다.)
먼저, 자바스크립트에서 리스트를 어떻게 변환하는지 살펴봅시다.
아래 주어진 코드는 map()함수를 가지고 numbers 배열의 값을 두 배로 만듭니다. map()함수로 리턴된 새로운 배열을 doubled라는 이름의 변수에 할당하고 doubled를 로그로 찍어서 확인합니다:
const numbers=[1,2,3,4,5];
const doubled=numbers.map((number)=>number*2);
console.log(doubled);
이 코드는 콘솔에 [2,4,6,8,10]
의 로그를 남깁니다.
리액트에서는, 배열을 element
의 리스트로 변환하는 것은 거의 동일합니다.
엘리먼트들의 콜렉션을 만들고 {}을 사용해서 엘리먼트들을 jsx에 포함시킬 수 있습니다.
아래의 코드를 보면, 자바스크립트 map() 함수를 사용해서numbers
배열을 반복 실행합니다. 각 아이템들이 <li>
엘리먼트에 반환됩니다. 결과적으로 만들어진 엘리먼트의 배열이 listItems
에 할당됩니다:
const numbers=[1,2,3,4,5];
const listItems=numbers.map((number)=>
<li>{number}</li>
);
우리는 listItems
배열 전체를 <ul>
에 포함시킬 수 있고, 그것이 DOM으로 렌더링됩니다:
ReactDom.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
보통 컴포넌트 안의 리스트를 렌더링하게 됩니다.
이전 예제의 numbers
배열을 받고 elements의 리스트를 출력하는 컴포넌트로 리팩토링 해보겠습니다.
function NumberList(props){
const numbers=props.numbers;
const listItmes=numbers.map((number)=>
<li>{number}</li>
);
return(
<ul>{listItmes}</ul>
);
}
const numbers=[1,2,3,4,5];
ReactDom.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
);
이 코드를 실행하면, 리스트 아이템들에 key가 주어져야 한다는 경고를 받을 것입니다. key는 엘리먼트 리스트를 만들 때 포함시켜줘야 하는 특별한 문자열 속성입니다. 다음 섹션에서 왜 이것이 중요한지 다뤄보겠습니다.
numbers.map()
안의 리스트 아이템들에 key를 할당하고, key가 없는 문제를 해결해봅시다.
function NumberList(props){
const numbers=props.numbers;
const listItmes=numbers.map((number)=>
<li key={number.toString()}>
{number}
</li>
);
return(
<ul>{listItmes}</ul>
);
}
const numbers=[1,2,3,4,5];
ReactDom.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
);
key는 리액트가 어떤 아이템이 바뀌었고, 추가되었고, 삭제되었는지 확인하는 것을 도와줍니다. 각 엘리먼트들이 안정되게 식별되게 하기 위해서 배열 안의 엘리먼트들에게 key가 부여됩니다 :
const numbers=[1,2,3,4,5];
const listItems=numbers.map((number)=>
<li key={number.toString()}>
{number}
</li>
);
key를 고르기 위한 최고의 방법은 형제 사이에서 리스트 아이템들을 고유하게 식별할 수 있는 문자열을 사용하는 것입니다. 가장 자주 쓰이는 방법은 데이터의 ID를 key로 사용하는 것입니다 :
const todoItmes=todos.map((todo)=>
<li key={todo.id}>
{todo.text}
</li>
);
렌더링된 아이템에게 안정된 ID가 없다면, 최후의 수단으로 아이템의 인덱스를 key로 사용해야 합니다 :
const todoItmes=todos.map((todo,index)=>
<li key={index}>
{todo.text}
</li>
);
아이템들의 순서가 바뀔 수 있기 때문에 인덱스를 사용하는 방법은 권장하지 않습니다. 이것은 성능에 부정적으로 영향을 끼칠 수 있고, 컴포넌트의 state에 문제가 발생할 수 있습니다. 인덱스를 키로 사용함으로 인한 부정적 영향에 대해 심층적으로 설명한 Robin Pokorny의 글을 확인해보세요. 만약 리스트 아이템에 확실한 키를 할당하지 않는다면, 리액트는 기본으로 인덱스를 키로 사용할 것입니다.
더 배우고 싶다면, 왜 키가 필요한 것인지에 대한 심층적 설명을 확인해보세요.
key는 주변 배열의 컨텍스트에서만 의미가 있습니다.
예를 들어 ListItem
컴포넌트를 추출하고자 한다면, ListItem
컴포넌트 안의<li>
엘리먼트의 키보다는 배열 안의 <ListItem/>
엘리먼트의 key를 가져야 합니다.
function ListItem(props) {
const value = props.value;
return (
//틀렸어요! 여기에 키를 명시할 필요가 없습니다
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems=numbers.map((number)=>
//틀렸어요! 키는 여기에 명시되었어야 했습니다
<ListItem value={number}/>
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
function ListItem(props) {
const value = props.value;
return (
//정답이에요! 키는 여기에 명시될 필요가 없습니다:
<li>{props.value}</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems=numbers.map((number)=>
//정답이에요! 키는 배열 안에 명시되어야만 합니다.
<ListItem key={number.toString()} value={number}/>
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
경험해보니, map() 함수 내의 엘리먼트는 key가 필요하다는 것을 알 수 있습니다.
배열에서 쓰이는 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: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
리액트에서 key는 힌트로 작용하지만 컴포넌트들에게 전달되지는 못합니다. 만약 같은 값이 컴포넌트에 필요하다면 명시적으로 다른 이름의 prop으로 key를 전달하세요.
const content=posts.map((post)=>
<Post
key={post.id}
id={post.id}
title={post.title}/>
);
위의 예시에서 보면, Post
컴포넌트는 props.id
는 읽을 수 있지만 props.key
는 읽을 수 없습니다.
위의 예시에서 별도의 ListItems
변수를 선언하고 그것을 JSX에 포함했습니다.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
JSX는 {}안의 어떤 표현도 포함시키기 때문에 map() 또한 포함시키는 것이 가능합니다.
function NumberList(props) {
const numbers=props.numbers;
return(
<ul>
{numbers.map((number)=>
<ListItem key={number.toString()} value={number}/>
)}
</ul>
);
}
이 방법은 가끔 더 깔끔한 코드를 작성할 수 있게 하기도 하지만, 이 스타일은 오용되기도 합니다. 자바스크립트와 마찬가지로 가독성을 위해서 변수를 추출해낼 것인지 아닌지 결정하는 것은 당신에게 달렸습니다. 만약 map() 의 body에 중첩이 심하다면 컴포넌트를 추출해내기 좋은 타이밍이라는 것을 명심하세요.