리스트 렌더링
- Published on
- 이 글은 리액트에서 데이터 배열을 처리하고 목록을 렌더링하는 방법을 설명하며, `map()`과 `filter()`를 활용하여 데이터를 다루는 방법을 다룹니다.
자주 데이터 컬렉션에서 여러 유사한 컴포넌트를 표시해야 할 때가 있습니다. JavaScript의 배열 메소드를 사용하여 데이터 배열을 조작할 수 있습니다. 이 페이지에서는 리액트에서 데이터 배열을 필터링하고 변환하여 컴포넌트의 배열로 렌더링하기 위해 filter()
와 map()
을 사용할 것입니다.
배열에서 데이터 렌더링하기
다음과 같은 내용의 목록이 있다고 가정해 보겠습니다.
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
이 목록 항목들 사이의 유일한 차이점은 그들의 내용, 즉 데이터입니다. 사용자 인터페이스를 구축할 때 동일한 컴포넌트의 여러 인스턴스를 다른 데이터를 사용하여 표시해야 할 때가 많습니다. 이러한 상황에서는 데이터를 JavaScript 객체와 배열에 저장하고 map()
과 filter()
와 같은 메소드를 사용하여 컴포넌트의 배열로부터 컴포넌트 목록을 렌더링할 수 있습니다.
다음은 배열에서 항목 목록을 생성하는 간단한 예입니다.
- 데이터를 배열로 이동시킵니다.
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
map()
메소드를 사용하여people
배열의 각 항목을 새로운 JSX 노드인listItems
로 매핑합니다.
const listItems = people.map(person => <li>{person}</li>);
- 컴포넌트에서
listItems
를 반환하며<ul>
로 감싸줍니다.
return <ul>{listItems}</ul>;
결과는 다음과 같습니다.
위의 샌드박스에서는 콘솔에 오류가 표시됩니다.
Warning: Each child in a list should have a unique “key” prop.
나중에 이 오류를 수정하는 방법을 알아보겠습니다. 하지만 먼저 데이터에 구조를 추가해 보겠습니다.
배열 필터링하기
데이터를 더 구조화할 수 있습니다.
const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];
이제 'chemist' 전문가만 표시하고 싶다고 가정해 보겠습니다. filter()
메소드를 사용하여 'chemist'인 사람들만 반환할 수 있습니다. 이 메소드는 항목 배열을 가져와 "테스트"라고 불리는 함수를 통과시키고 테스트를 통과한 항목만으로 이루어진 새로운 배열을 반환합니다.
'chemist' 전문가인 항목만 필요합니다. 이를 위한 "테스트" 함수는 다음과 같습니다: person => person.profession === 'chemist'
. 다음과 같이 구성해 보겠습니다.
filter()
를 사용하여 "chemist" 사람들의 배열인chemists
를 생성합니다. 이때person.profession === 'chemist'
로 필터링합니다.
const chemists = people.filter(person =>
person.profession === 'chemist'
);
chemists
에 대해map()
을 사용합니다.
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
- 마지막으로 컴포넌트에서
listItems
를 반환합니다.
return <ul>{listItems}</ul>;
주의: 화살표 함수는 =>
다음에 오는 표현식을 암묵적으로 반환하므로 return 문을 사용하지 않아도 됩니다.
const listItems = chemists.map(person =>
<li>...</li> // 암묵적 반환!
);
하지만 =>
다음에 중괄호 {}
가 오는 경우에는 명시적으로 return 문을 작성해야 합니다.
const listItems = chemists.map(person => { // 중괄호
return <li>...</li>;
});
=>
다음에 중괄호가 있는 화살표 함수를 블록 본문(block body)이라고 합니다.
이를 사용하면 한 줄 이상의 코드를 작성할 수 있지만, 명시적으로 return 문을 작성해야 합니다. 누락되면 반환되는 값이 없습니다!
key
를 사용하여 목록 항목 순서 유지하기
앞서 언급한 샌드박스 모두 콘솔에 오류가 표시됩니다.
Warning: Each child in a list should have a unique “key” prop.
각 배열 항목에 고유한 식별자인 key를 제공해야 합니다. key는 문자열 또는 숫자로 이루어진 값으로, 해당 배열의 다른 항목들과 구별할 수 있어야 합니다.
<li key={person.id}>...</li>
참고: JSX 요소는 항상 map() 호출 내에서 키가 필요합니다!
키는 리액트에게 각 컴포넌트가 어떤 배열 항목에 해당하는지 알려줍니다. 이렇게 함으로써 컴포넌트를 추후에 매칭할 수 있습니다. 배열 항목이 이동(정렬 때문에), 삽입되거나 삭제되는 경우에 중요한 역할을 합니다. 잘 선택된 키는 리액트에게 변화가 발생한 내용을 추론하고 DOM 트리를 올바르게 업데이트할 수 있게 도와줍니다.
키를 동적으로 생성하는 대신 데이터에 포함시켜야 합니다.
자세히 알아보기: 한 항목마다 하나 이상의 DOM 노드를 렌더링해야 하는 경우 어떻게 해야 할까요?
간단한 <>...</>
Fragment 구문에서는 키를 전달할 수 없으므로, 여러 DOM 노드를 단일 <div>
로 그룹화하거나, 약간 더 긴 <Fragment>
구문을 사용해야 합니다.
import { Fragment } from 'react';
// ...
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
Fragments는 DOM에서 사라지므로 <h1>, <p>, <h1>, <p>
와 같은 평면 목록을 생성합니다.
key
를 얻는 위치
데이터의 소스에 따라 다양한 키 소스가 제공됩니다.
- 데이터베이스에서 데이터 가져오기: 데이터가 데이터베이스에서 가져오는 경우, 고유한 키 또는 ID를 사용할 수 있습니다.
- 로컬에서 생성된 데이터: 데이터가 로컬에서 생성되고 유지되는 경우(예: 메모 앱의 메
모), 증가하는 카운터, crypto.randomUUID() 또는 uuid
와 같은 패키지를 사용하여 항목을 생성할 때 사용할 수 있습니다.
키의 규칙
- 키는 형제 사이에서 고유해야 합니다. 그러나 서로 다른 배열에서 JSX 노드에 동일한 키를 사용하는 것은 괜찮습니다.
- 키는 변경되지 않아야 합니다. 그렇지 않으면 키의 목적이 상실됩니다! 렌더링 중에 키를 생성하지 마세요.
리액트가 키가 필요한 이유
만약 데스크톱의 파일들이 이름을 가지지 않고 대신 순서로 참조해야 한다고 상상해보세요. 첫 번째 파일, 두 번째 파일 등으로 참조해야 할 것입니다. 그것에 익숙해질 수 있겠지만, 한 번 파일을 삭제하면 혼란스러워질 것입니다. 두 번째 파일이 첫 번째 파일이 되고, 세 번째 파일이 두 번째 파일이 되는 식으로 계속될 것입니다.
폴더의 파일 이름과 JSX의 키는 비슷한 목적을 가지고 있습니다. 이들은 우리가 형제 사이에서 항목을 고유하게 식별할 수 있도록 도와줍니다. 잘 선택된 키는 배열 내에서의 위치보다 더 많은 정보를 제공합니다. 항목이 재정렬되거나 삽입 또는 삭제되는 경우에도 키를 통해 리액트는 항목을 정확히 식별하고, 그 수명 동안 항목을 올바르게 처리할 수 있습니다.
항목의 인덱스를 키로 사용하려는 유혹을 느낄 수 있습니다. 사실, 키를 지정하지 않으면 리액트가 인덱스를 사용합니다. 그러나 항목이 삽입되거나 삭제되거나 배열이 재정렬되면 렌더링되는 항목의 순서가 변경됩니다. 인덱스를 키로 사용하는 것은 종종 subtile하고 혼란스러운 버그로 이어집니다.
마찬가지로 키를 동적으로 생성하지 마세요. 예를 들어 key={Math.random()}
와 같이 동적으로 키를 생성하면 키가 렌더링 사이에 일치하지 않아 모든 컴포넌트와 DOM이 매번 재생성됩니다. 이는 느리고 목록 항목 내부의 사용자 입력을 잃어버릴 수 있습니다. 대신 데이터를 기반으로 안정적인 ID를 사용하세요.
참고로 컴포넌트는 키를 프롭으로 받지 않습니다. 키는 리액트 자체에 의해 힌트로 사용되는 것입니다. 컴포넌트가 ID를 필요로 하는 경우 별도의 프롭으로 전달해야 합니다: <Profile key={id} userId={id} />
.
요약
이 페이지에서 다음을 배웠습니다:
- 컴포넌트에서 데이터를 컴포넌트 외부의 배열과 객체와 같은 데이터 구조로 이동하는 방법
- JavaScript의
map()
을 사용하여 유사한 컴포넌트 집합을 생성하는 방법 - JavaScript의
filter()
를 사용하여 필터링된 항목의 배열을 생성하는 방법 - 각 컴포넌트에 키를 설정하여 리액트가 위치 또는 데이터가 변경되더라도 각 항목을 추적할 수 있도록 하는 방법