(React 공식문서의 main concepts 번역 글입니다.)
함께보면 이해가 쏙쏙 https://youtu.be/7xZMLyfsQc8
component로 list를 구성할 때
key를 활용하여 각 component를 구분해야 한다.
그래야만 ReactDOM이 각 component를 비교할 수 있기 때문이다.
component로 list를 구성하기 전에
javascript에서는 어떻게 list의 각 값을 수정했는지 보자.
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(number => number * 2);
console.log(doubled);
console에 [2, 4, 6, 8, 10] 이 찍히는 것을 확인할 수 있다.
React에서 component로 list를 구성하는 것도 이와 비슷하다.
elements를 item으로 갖는 array를 만들 수 있다.
이 array를 {}
를 통해 JSX안에서 사용할 수 있다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(number =>
<li>{number}</li>
);
<li>{number}</li>
는 element를 return하는 component이다.
이 element들은 listItems라는 array의 item이다.
이 array는 element(<li>
tag)들의 집합이므로
<ul>
로 감싼 후, ReactDOM.render()에게 전달하여 rendering한다.
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
다수의 component를 rendering하는 코드가 완성되었다.
component들로 구성된 list를 return하는 component를 만들어보자.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(number => <li>{number}</li> ); return (
<ul>{listItems}</ul> )
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />, document.getElementById('root')
);
위 코드를 실행하면 경고 가 뜬다!
react에서 list는 각 item마다 key값이 필요하기 때문이다.
key 는 string값을 가지는 특수한 props이다. react에서 list는 각 item마다 key 값을 주어야 한다.
위 list의 각 item(<li>{number}</li>
)에 key값을 주어
경고 를 해결하자.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> {number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
key는 list의 각 item이 수정되거나 item이 추가, 삭제될 때
react가 그것을 빠르게 알아차릴 수 있도록 도와준다.
map을 실행할 때 item에게 반드시 key값이 주어져야 한다.
그래야 item, 즉 react element가 stable한 identity를 가질 수 있다.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}> {number}
</li>
);
가장 좋은 key 값은 다른 element들과 다른 unique한 string 값이다.
대부분은 data의 id값을 key값으로 준다.
const todoItems = todos.map((todo) =>
<li key={todo.id}> {todo.text}
</li>
);
만약 stable한 key값을 주기가 정말 어렵다면 index를 key값으로 써라.
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs <li key={index}> {todo.text}
</li>
);
item의 index를 key값으로 주는 것은 추천하지 않는다.
item의 순서가 바뀔 수 있기 때문이다.(index값이 stable하지 않음)
그 결과, react performance가 떨어질 수 있다.
또한 component의 state에 관한 issue가 발생 할 수도 있다.
(index를 key값으로 주면 안좋은 이유에 대해 추가설명한 링크
https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318)
만약 key값을 주지 않으면, react는 우선 index를 key로 사용 한다.
왜 key가 react performance와 연관있는 걸까?
자세히 알아보려면 아래 링크를 확인하자
(https://reactjs.org/docs/reconciliation.html#recursing-on-children)
keys값은 array의 item을 map으로 바꾸는 과정에서 주어져야 한다.
extract한 component가 element를 return 할 때
key값을 주는 것은 의미가 없다.
key값을 잘 못 준 경우(ReactDOM.render는 생략)
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here: <li key={value.toString()}> {value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here: <ListItem value={number} /> );
return (
<ul>
{listItems}
</ul>
);
}
key값을 바르게 준 경우(ReactDOM.render는 생략)
function ListItem(props) {
// Correct! There is no need to specify the key here: return <li>{props.value}</li>;}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array. <ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
siblings란 array안에 함께 있는 다른 component들을 말한다.
이 component들과는 구별되기 위해 unique한 key값을 가져야한다.
하지만 global하게 모든 component들과 다른, unique한 key값을 가질 필요는 없다.
즉, 다른 component들의 집합안에 있다면 같은 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값을 각 list의 item(<div>
와 <li>
)에게 주고 있다.
<div>
return하는 component와 <li>
return하는 component
다른 component들의 모음이기 때문에
같은 key값(post.id)을 할당해도 error가 발생하지 않는다.
key값은 React가 알아차리는데 도움을 줄 뿐,
component에게 props로 전달되지 않는다.
따라서, key값을 component 안에서 활용하고 싶다면
새로운 다른 props로 값을 할당해주어야 한다.
const content = posts.map((post) =>
<Post
key={post.id} id={post.id} title={post.title} />
);
위에서 처럼, post.id값을 content안에서 props로 활용하고 싶다면
id라는 props를 통해 그 값을 새롭게 다시 할당해 주어야 한다.
아래 코드에서는 listItems 변수에 component의 lists를 할당하고
JSX안에서 그 변수를 {listItems}
로 넣은 것을 볼 수 있다.
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) => <ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems} </ul>
);
}
JSX는 어떠한 javascript expression도 넣을 수 있으므로
{}
안에 map 전체를 넣을 수도 있다.
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul>
);
}
가끔 map 전체를 JSX안에 넣으면 코드가 더 깔끔해질 수도 있다.
가독성이 더 좋은 방향으로 코드를 짜면 된다.
만약 map 안에서 여러 브라켓으로 nested 되어있다면
component를 extract하는 것이 더 가독성이 좋을 것이다.