리스트 렌더링

데이터 집합에서 여러 개의 유사한 컴포넌트들을 표시하려는 경우가 많습니다. JavaScript 배열 메서드를 사용하여 데이터 배열을 조작할 수 있습니다. 이 페이지에서는 filter()map()을 React와 사용해 데이터 배열을 컴포넌트 배열로 필터링하고 변환해보겠습니다.

You will learn

  • JavaScript의 map()을 사용하여 배열을 컴포넌트로 렌더링하는 방법
  • JavaScript의 filter()를 사용하여 특정 컴포넌트만 렌더링하는 방법
  • React에서 Key의 사용 시점 및 이유

배열을 데이터로 렌더링하기

내용이 있는 리스트가 있다고 가정해봅시다.

<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() 같은 메서드를 사용할 수 있습니다.

배열에서 항목의 리스트를 생성하는 간단한 예시입니다.

  1. 데이터를 배열로 이동하기
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
  1. people의 요소들을 새로운 JSX 노드의 배열인 listItems매핑하기
const listItems = people.map(person => <li>{person}</li>);
  1. 컴포넌트에서 listItems<ul>로 래핑해서 반환하기
return <ul>{listItems}</ul>;

결과는 다음과 같습니다.

const people = [
  'Creola Katherine Johnson: mathematician',
  'Mario José Molina-Pasquel Henríquez: chemist',
  'Mohammad Abdus Salam: physicist',
  'Percy Lavon Julian: chemist',
  'Subrahmanyan Chandrasekhar: astrophysicist'
];

export default function List() {
  const listItems = people.map(person =>
    <li>{person}</li>
  );
  return <ul>{listItems}</ul>;
}

배열의 항목들을 필터링하기

이 데이터는 훨씬 더 구조화될 수 있습니다.

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’인 사람들만 보여주고 싶다고 생각해봅시다. 해당하는 사람들만 반환하기 위해 JavaScript의 filter() 메서드를 사용할 수 있습니다. 이 메서드는 배열을 가져와서 “test” (truefalse를 반환하는 함수)로 항목을 넘겨주고 test에 통과된 항목(true가 반환된 항목)만 있는 새로운 배열을 반환합니다.

직업’chemist’인 항목만 필요합니다. 이를 위한 “test” 함수는 (person) => person.profession === 'chemist'와 같을 것입니다. 이것을 적용하는 과정은 다음과 같습니다.

  1. people에서 filter()를 호출해 person.profession === 'chemist'로 필터링해서 “chemist”인 사람만 있는 새로운 배열 chemists생성합니다.
const chemists = people.filter(person =>
person.profession === 'chemist'
);
  1. 이제 chemists매핑합니다.
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>
);
  1. 마지막으로 컴포넌트에서 listItems반환합니다.
return <ul>{listItems}</ul>;
import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const chemists = people.filter(person =>
    person.profession === 'chemist'
  );
  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>
  );
  return <ul>{listItems}</ul>;
}

Pitfall

화살표 함수는 => 바로 뒤에 오는 표현 식을 암시적으로 반환하기 때문에 return 구문이 필요하지 않습니다.

const listItems = chemists.map(person =>
<li>...</li> // 암시적 반환!
);

하지만 => 뒤에 { 중괄호가 이어진다면 외부적으로 return을 명시적으로 써야 합니다!

const listItems = chemists.map(person => { // 중괄호
return <li>...</li>;
});

=> { 를 표현하는 화살표 함수를 “block body”를 가지고 있다고 말합니다. 이것은 코드를 여러 줄로 작성할 수 있게 해주지만 return 구문을 반드시 작성해야 합니다. 그렇지 않으면 아무것도 반환되지 않습니다!

key를 사용해서 리스트 항목을 순서대로 유지하기

위의 샌드박스 중 하나를 새 탭에서 열면 콘솔에서 다음과 같은 에러를 볼 수 있습니다.

Console
Warning: Each child in a list should have a unique “key” prop.

배열의 다른 항목 간에 고유하게 식별되는 문자열이나 숫자 형태의 key를 각 배열 항목에 주어야 합니다.

<li key={person.id}>...</li>

Note

JSX elements directly inside a map() call always need keys!

Key는 각 컴포넌트가 어떤 배열 항목에 해당하는지 알려주어 React가 나중에 그것들을 일치시킬 수 있도록 해줍니다. 이것은 예를 들어 정렬로 인해 배열 항목이 이동, 삽입 또는 삭제되는 경우 중요한 문제가 됩니다. 잘 선택된 key는 React가 정확히 무슨 일이 일어났는지 유추해서 DOM 트리에 적절하게 반영할 수 있도록 도와줍니다.

즉석에서 key를 생성하는 대신 데이터 안에 key를 포함해야 합니다.

export const people = [{
  id: 0, // JSX에서 key로 사용됩니다.
  name: 'Creola Katherine Johnson',
  profession: 'mathematician',
  accomplishment: 'spaceflight calculations',
  imageId: 'MK3eW3A'
}, {
  id: 1, // JSX에서 key로 사용됩니다.
  name: 'Mario José Molina-Pasquel Henríquez',
  profession: 'chemist',
  accomplishment: 'discovery of Arctic ozone hole',
  imageId: 'mynHUSa'
}, {
  id: 2, // JSX에서 key로 사용됩니다.
  name: 'Mohammad Abdus Salam',
  profession: 'physicist',
  accomplishment: 'electromagnetism theory',
  imageId: 'bE7W1ji'
}, {
  id: 3, // JSX에서 key로 사용됩니다.
  name: 'Percy Lavon Julian',
  profession: 'chemist',
  accomplishment: 'pioneering cortisone drugs, steroids and birth control pills',
  imageId: 'IOjWm71'
}, {
  id: 4, // JSX에서 key로 사용됩니다.
  name: 'Subrahmanyan Chandrasekhar',
  profession: 'astrophysicist',
  accomplishment: 'white dwarf star mass calculations',
  imageId: 'lrWQx8l'
}];

Deep Dive

각 리스트 항목에 대해 여러 개의 DOM 노드 표시하기

각 항목이 하나가 아닌 여러 개의 DOM 노드들에 렌더링해야 하는 경우에는 어떻게 해야 할까요?

짧은 <> </> fragment 문법은 key를 전달할 수 없기 때문에 그것들을 단일한 <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를 어디에서 가져올까요?

다양한 데이터 소스가 다양한 key 소스를 제공합니다

  • 데이터베이스의 데이터: : 데이터베이스에서 데이터를 가져오는 경우 본질적으로 고유한 데이터베이스의 key 및 ID를 사용할 수 있습니다.
  • 로컬에서 생성된 데이터: 노트작성 앱의 노트처럼 데이터가 로컬에서 생성되고 유지되는 경우에는 항목을 생성할 때 증가하는 일련번호나 crypto.randomUUID() 또는 uuid 같은 패키지를 사용해야 합니다.

key 규칙

  • key는 형제간에 유일해야 합니다. 하지만 같은 key를 다른 배열에 있는 JSX 노드가 사용하는 것은 괜찮습니다.
  • key는 바뀔 수 없으며 그렇게 되면 key는 목적을 잃게 됩니다. 렌더링 중에는 key를 생성하지 마십시오.

React에 key가 왜 필요할까요?

데스크탑에 있는 파일에 이름이 없다고 상상해 봅시다. 대신 첫 번째 파일, 두 번째 파일 등의 순서로 그것들을 참조하는 겁니다. 익숙해질 수도 있지만, 파일을 삭제한다면 혼란스러워질 것입니다. 두 번째 파일은 첫 번째 파일이 될 것이고 세 번째 파일은 두 번째 파일이 되는 식입니다.

폴더에 있는 파일명과 배열의 JSX key는 비슷한 용도로 사용됩니다. 이를 통해 형제간 항목들을 고유하게 식별할 수 있습니다. 잘 선택된 key는 배열 내 위치보다 더 많은 정보를 제공합니다. 재 정렬로 _위치_가 변경되어도 key는 React가 항목의 생명주기 동안 해당 항목을 식별할 수 있게 해줍니다.

Pitfall

배열에서 항목의 인덱스를 key로 사용하고 싶을 것입니다. 실제로 key를 지정하지 않는 경우 React는 인덱스를 사용합니다. 하지만 항목을 삽입, 삭제하거나 배열이 재 정렬될 경우 항목을 렌더링하는 순서는 시간의 흐름에 따라 변경될 것입니다. 인덱스를 key로 사용하면 종종 미묘하고 혼란스러운 버그가 발생합니다.

마찬가지로 key={Math.random()}처럼 즉석에서 key를 생성하지 마십시오. 이러한 방식은 렌더링 사이에서 key가 일치하지 않게 되어 모든 컴포넌트와 DOM이 매번 재생성됩니다. 이것은 느릴 뿐만 아니라 리스트 항목 내부의 모든 사용자의 입력을 누락시킵니다. 대신 데이터 기반의 안정적인 ID를 사용하세요.

컴포넌트가 key를 prop로 받지 않는다는 것에 유의하세요. key는 React 자체에서 힌트로만 사용됩니다. 컴포넌트에 ID가 필요하다면 <Profile key={id} userId={id} />와 같이 별도의 prop로 전달해야 합니다.

Recap

이 페이지에서 학습한 내용

  • 컴포넌트에서 배열 및 객체와 같은 데이터 구조로 데이터를 이동하는 방법
  • JavaScript의 map()을 사용하여 유사한 컴포넌트 집합을 생성하는 방법
  • JavaScript의 filter()를 사용하여 필터링 된 항목의 배열을 생성하는 방법
  • 컬렉션에서 각 컴포넌트에 key를 설정하여 위치나 데이터가 변경되더라도 React가 각 컴포넌트를 추적할 수 있도록 하는 이유 및 방법

Challenge 1 of 4:
리스트를 둘로 나누기

예시는 모든 사람의 리스트를 보여줍니다.

두 개의 개별 리스트 ChemistsEveryone Else을 차례로 표시하도록 변경하세요. 이전과 마찬가지로 person.profession === 'chemist'를 확인하여 누가 chemist인지 여부를 확인할 수 있습니다.

import { people } from './data.js';
import { getImageUrl } from './utils.js';

export default function List() {
  const listItems = people.map(person =>
    <li key={person.id}>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}:</b>
        {' ' + person.profession + ' '}
        known for {person.accomplishment}
      </p>
    </li>
  );
  return (
    <article>
      <h1>Scientists</h1>
      <ul>{listItems}</ul>
    </article>
  );
}