여러개 컴포넌트 렌더링하기
기존의 JavaScript에서는 다음 코드와 같이 map()
함수를 이용하여 numbers
배열의 값을 두배로 만든 후 map()
에서 반환하는 새 배열을 doubled
변수에 할당하고 로그를 확인하는 방식으로 리스트를 변환합니다.
const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((number) => number * 2); console.log(doubled); // [2, 4, 6, 8, 10]
React에서 배열을 엘리먼트 리스트로 만드는 방식도 map() 함수를 이용하는데, 위 코드의 Javascript 방식과 거의 동일합니다.
React는 엘리먼트 모음을 만들고 중괄호{}를 이용하여 JSX에 포함시킬 수 있는데, 다음 코드와 같이 JavaScript의 map()
함수를 사용하여 numbers
배열을 반복 실행하면 각 항목에 대해 <li>
엘리먼트를 반환하고 엘리먼트 배열의 결과를 listItems
에 저장할 수 있습니다.
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li>{number}</li> );
그 다음 listItems
배열을 <ul>
엘리먼트 안에 포함하고 DOM에 렌더링하면, 1부터 5까지의 숫자로 이루어진 리스트가 출력되는 것을 확인할 수 있습니다.
ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
기본 리스트 컴포넌트
일반적으로는 컴포넌트 안에서 리스트를 렌더링하는데, 앞의 예제를 다음과 같이 numbers 배열을 받아 순서 없는 엘리먼트 리스트를 출력하는 컴포넌트로 리팩토링할 수 있습니다.
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') );
위 코드를 실행하면 리스트의 각 항목에 key
를 넣어야 한다는 경고가 표시됩니다. key는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트라고 할 수 있는데, 다음과 같이 numbers.map()
안에 있는 리스트의 각 항목에 key
를 할당하면 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
Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는 속성으로, 엘리먼트에 안정적인 고유성을 부여할 수 있도록 배열 내부의 엘리먼트에 지정해 주어야 합니다.
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> );
Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것인데, 대부분 데이터의 ID
를 key
로 사용하는 것이 가장 좋은데, 렌더링 한 항목에 대한 안정적인 ID
가 없는 경우에는 항목의 인덱스를 key
로 사용할 수도 있습니다.
const todoItems = todos.map((todo) => <li key={todo.id}> {todo.text} </li> );
const todoItems = todos.map((todo, index) => // 항목에 안정적인 ID가 없는 경우에만 사용 <li key={index}> {todo.text} </li> );
하지만 항목의 순서가 바뀔 수 있는 경우에는 key
에 인덱스를 사용하는 것은 권장하지 않는데, 이로 인해 성능이 저하되거나 컴포넌트의 state
와 관련된 문제가 발생할 수 있기 때문입니다.
만약 리스트 항목에 명시적으로 key
를 지정하지 않으면 React는 기본적으로 인덱스를 key
로 사용하게 됩니다.
Key로 컴포넌트 추출하기
키는 주변 배열의 context에서만 의미가 있는데, 이는 ListItem
컴포넌트를 추출 한 경우 ListItem
안에 있는 <li>
엘리먼트가 아닌 배열의 <ListItem />
엘리먼트가 key
를 가져야 한다는 의미입니다.
function ListItem(props) { const value = props.value; return ( // (X) 여기에는 key를 지정할 필요가 없음 <li key={value.toString()}> {value} </li> ); } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // (X) 여기에 key를 지정해야 됨 <ListItem value={number} /> ); return ( <ul> {listItems} </ul> ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( <NumberList numbers={numbers} />, document.getElementById('root') );
위의 코드는 key
의 잘못된 사용 예를 보여주는데, key
의 올바른 사용법은 다음과 같습니다.
function ListItem(props) { // (O) 여기에는 key를 지정할 필요가 없음 return <li>{props.value}</li>; } function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // (O) 배열 안에 key를 지정 <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') );
Key의 고유값 설정 범위
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') );
React에서 key
는 힌트를 제공하지만 컴포넌트로 전달하지는 않는데, 컴포넌트에서 key
와 동일한 값이 필요하면 다른 이름의 prop
을 명시적으로 전달하면 됩니다. 다음의 코드에서 Post
컴포넌트는 props.id
를 읽을 수 있지만 props.key
는 읽을 수 없습니다.
const content = posts.map((post) => <Post key={post.id} id={post.id} title={post.title} /> );
JSX에 map() 포함시키기
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <ListItem key={number.toString()} value={number} /> ); return ( <ul> {listItems} </ul> ); }
위 코드는 별도의 listItems
변수를 선언하고 이를 JSX에 포함했는데, JSX를 사용하면 중괄호{} 안에 모든 표현식을 포함 시킬 수 있기 때문에 map()
함수의 결과를 인라인으로 처리할 수 있습니다.
function NumberList(props) { const numbers = props.numbers; return ( <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul> ); }
이 방식을 사용하면 코드가 더 깔끔해 지지만, 남발하는 것은 좋지 않습니다. JavaScript와 마찬가지로 가독성을 위해 변수로 추출해야 할지 아니면 인라인으로 넣을지는 개발자가 직접 판단할 필요가 있는데, map()
함수가 너무 중첩되는 경우라면 컴포넌트로 추출 하는 것을 고려해 보는 것이 좋습니다.