[리액트 공식문서 읽기] DESCRIBING THE UI - Rendering Lists

JaeHong Jeong·2023년 8월 20일
post-thumbnail

Overview

데이터 모음에서 유사한 여러 컴포넌트를 나열하고 싶은 경우가 많다. JavaScript array methods를 사용하여 데이터 배열을 조작할 수 있다. 이 페이지에서는 리액트와 함께 filter()map() 을 사용하여 데이터 배열을 필터링하고 컴포넌트 배열로 변환한다.

Rendering data from arrays

컨텐츠 목록이 있다고 가정한다.

<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. Move the data into an array:

    const people = [
      'Creola Katherine Johnson: mathematician',
      'Mario José Molina-Pasquel Henríquez: chemist',
      'Mohammad Abdus Salam: physicist',
      'Percy Lavon Julian: chemist',
      'Subrahmanyan Chandrasekhar: astrophysicist'
    ];
  2. Map the people members into a new array of JSX nodes, listItems:

    const listItems = people.map(person => <li>{person}</li>);
  3. Return listItems from your component wrapped in a <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>;
}

콘솔에 오류가 표시된다.

이 페이지의 뒷부분에서 이 오류를 수정하는 방법을 배우게 된다. 이에 도달하기 전에 데이터에 일부 구조를 추가해 보겠다.

Filtering arrays of items

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

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”(true 또는 false 를 반환하는 함수)를 통해 전달하고 테스트를 통과한(true반환) 항목만 표함된 새 배열을 반환한다.

profession'chemist' 인 항목만 원한다. 이에 대한 “test”함수는 (person) => person.profession === 'chemist' 와 같다. 조합하는 방법은 다음과 같다

  1. Create a new array of just “chemist” people, chemists, by calling filter() on the people filtering by person.profession === 'chemist':

    const chemists = people.filter(person =>
      person.profession === 'chemist'
    );
  2. Now map over 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>
    );
  3. Lastly, return the listItems from your component:

    return <ul>{listItems}</ul>;
// App.js

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>;
}
// data.js

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

export function getImageUrl(person) {
  return (
    'https://i.imgur.com/' +
    person.imageId +
    's.jpg'
  );
}
💡 Pitfall

화살표 함수는 => 바로 다음에 암묵적으로 식을 반환하므로 return 문이 필요하지 않는다

const listItems = chemists.map(person =>
  <li>...</li> // Implicit return!
);

하지만 => 다음에 { 중괄호가 있으면 return 를 적어줘야한다.

const listItems = chemists.map(person => { // Curly brace
  return <li>...</li>;
})

=> { 를 포함하는 화살표 함수는 “block body”을 갖는다고 한다. 한 줄 이상의 코드를 작성할 수 잇찌만 return 는 직접 작성해야한다. 잊어버리면 아무것도 반환되지 않는다.

Keeping list items in order with key

위의 모든 샌드박스는 콘솔에 오류를 표시한다.

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

각 배열 항목에 key(해당 배열의 다른 항목 중에서 고유하게 식별하는 문자열 또는 숫자)를 제공해야 한다.

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

map() 호출 바로 내부의 JSX 요소에는 항상 키가 필요하다.

키는 리액트에게 각 컴포넌트가 해당하는 배열 항목을 알려주므로 나중에 매치시킬 수 있다. 배열 항목이 이동(예 : 정렬로 인해), 삽입 또는 삭제될 수 있는 경우 이는 중요해진다. 잘 선택된 key 는 리액트가 정확히 무슨 일이 일어났는지 추론하고 DOM 트리를 올바르게 업데이트 하는 데 도움이 된다.

즉석에서 키를 생성하는 대신 데이터에 키를 포함해야 한다.

// App.js

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 <ul>{listItems}</ul>;
}
// data.js

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

export function getImageUrl(person) {
  return (
    'https://i.imgur.com/' +
    person.imageId +
    's.jpg'
  );
}
💡 DEEP DIVE

각 목록 항목에 대해 여러 DOM 노드 표시

각 항목이 하나가 아닌 여러 DOM 노드를 렌더링해야 하는 경우 어떻게 해야하나?
짧은 [<>...</> Fragment](https://react.dev/reference/react/Fragment) 구문으로는 키를 전달할 수 없으므로 키를 단일 <div> 로 그룹화하거나 약간 더 길고 더 명확한 more explicit <Fragment> 구문을 사용해야 한다.

import { Fragment } from 'react';

// ...

const listItems = people.map(person =>
  <Fragment key={person.id}>
    <h1>{person.name}</h1>
    <p>{person.bio}</p>
  </Fragment>
);

DOM에서 Fragments는 사라지므로 <h1>, <p>, <h1>, <p> 등의 플랫 목록이 생성된다.

Where to get your key

서로 다른 데이터 소스는 서로 다른 키 소스를 제공한다.

  • 데이터베이스의 데이터 : 데이터가 데이터베이스에서 오는 경우 본질적으로 고유한 데이터베이스 키/ID를 사용할 수 있다.
  • 로컬에서 생성된 데이터: 데이터가 로컬에서 생성되고 지속되는 경우(예: 메모 작성 앱의 메모) 항목을 생성할 때 증분 카운터, crypto.randomUUID() 또는 uuid 와 같은 패키지를 사용해라.

Rules of keys

  • 키는 형제 간에 고유해야 한다. 그러나 다른 배열의 JSX 노드에 동일한 키를 사용해도 괜찮다.
  • 키는 변경되어서는 안되며, 키의 목적에 어긋나면 안된다. 렌더링하는 동안 생성하지 마라.

Why does React need keys?

데스크톱의 파일에 이름이 없다고 상상해 보아라. 대신 첫 번째 파일, 두 번째 파일 등의 순서로 참조할때, 익숙해질 수 있지만 일단 파일을 삭제하면 혼란스러워질 것이다. 두 번째 파일은 첫 번째 파일이 되고 세 번째 파일은 두 번째 파일이 되는 식이다.

폴더의 파일 이름과 배열의 JSX 키는 비슷한 용도로 사용된다. 이를 통해 형제 간에 항목을 고유하게 식별할 수 있다. 잘 선택된 키는 배열 내의 위치보다 더 많은 정보를 제공한다. 재정렬로 인해 위치가 변경되더라도 key 를 통해 리액트는 전체 생명 주기 동안 항목을 식별할 수 있다.

💡 Pitfall

배열에서 항목의 인덱스를 키로 사용하고 싶은 유혹을 느낄 수 있다. 실제로 key를 전혀 지정하지 않으면 리액트가 인덱스를 사용하는 것이다. 하지만 항목을 삽입하거나 삭제하거나 배열이 재정렬되는 경우 항목을 렌더링하는 순서는 시간이 지남에 따라 변경된다. 인덱스를 키로 사용하면 종종 미묘하고 혼란스러운 버그가 발생한다.

마찬가지로 key={Math.random()} 으로 즉석에서 키를 생성하면 안된다. 이로 인해 렌더링 간에 키가 일치하지 않아 컴포넌트와 DOM이 매번 다시 생성된다. 이것은 느릴 뿐만 아니라 배열 항목 내부의 사용자 입력도 잃게 된다. 대신 데이터를 기반으로 안정적인 ID를 사용해라.

컴포넌트는 key 를 props로 받지 않는다. 리액트 자체에서 힌트로만 사용된다. 컴포넌트에 ID가 필요한 경우 <Profile key={id} userId={id} /> 와 같이 별도의 props로 전달해야 한다.

Recap

  • 컴포넌트에서 배열 및 객체와 같은 데이터 구조로 데이터를 이동하는 방법
  • JavaScript의 map() 으로 유사한 컴포넌트 셋을 생성하는 방법
  • JavaScript의 filter() 로 필터링된 항목의 배열을 만드는 방법
  • 위치나 데이터가 변경되더라도 리액트가 각 컴포넌트를 추적할 수 있도록 컬렉션의 각 컴포넌트에 key 를 설정하는 이유와 방법
profile
반갑습니다.

0개의 댓글