IT

[영어로 배우는 React] 8. 리스트와 키

생각파워 2022. 4. 20. 11:51

공식 사이트 : https://reactjs.org/docs/lists-and-keys.html

 

 

먼저 자바스크립트에서 리스트를 어떻게 변형시키는지 살펴보겠습니다. 

아래의 코드는 배열에서 값을 가져오고, 그 값을 두배로 만드는데 map() 함수를 사용합니다. map() 함수에 의해 리턴된 값을 새로운 배열에 할당하고, console.log() 함수를 이용해 내용을 출력해봅니다.

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

이 코드는 콘솔에 [2, 4, 6, 8, 10]을 출력합니다. 

리액트에서 배열을 앨리먼트의 리스트로 변형하는 것은 위 방식과 거의 동일합니다.

 

 

다수의 컴포넌트 랜더링하기

리액트에서는 앨리먼트 모음을 만들 수 있고, 중괄호(curly braces)를 이용하여 JSX에 포함할 수 있습니다. 아래의 예제에서 numbers 배열을 map() 함수를 이용해서 루프를 돌리고 있습니다. 각각의 값들에 대해 <li> 앨리먼트를 리턴받습니다. 최종적으로, listItems 변수에 앨리먼트 배열을 할당합니다. 

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>);

이후, 아래와 같이 <ul> 앨리먼트 안에 listItems 전체를 포함시킬 수 있습니다. 

<ul>{listItems}</ul>

실행결과는 다음과 같습니다.

이 코드는 글머리 기호가 달린 1과 5 사이 숫자를 출력합니다.

 

 

기본 리스트 컴포넌트 

보통 컴포넌트 내부의 리스트를 랜더링하게 될 것입니다. 우리는 앞 예제를 숫자 배열을 받아들여, 앨리먼트를 출력하는  컴포넌트로 재구성할 수 있습니다. 

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];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers} />);

이 코드를 실행하면, 리스트의 아이템들에 대해 키를 제공해야 된다는 경고를 볼 수 있을 겁니다. 키는 앨리먼트 리스트를 만들 때 필요한 특별한 문자 속성입니다. 키가 왜 중요한지, 다음 섹션에서 살펴보겠습니다.

그럼 numbers.map() 내부의 아이템 리스트들에 대해 키값을 부여해서 키 값에 대한 경고를 없애보도록 하겠습니다.

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>      
    	{number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

 

 

키(keys)

리액트에서 키는 어떤 아이템이 변경, 추가, 삭제되었는지 식별하는 데 사용됩니다. 앨리먼트에게 안정적인 ID를 부여하기 위해 키를 제공합니다. 키를 정하는 최고의 방법은 리스트 아이템을 유일하게 식별할 수 있는 문자를 사용하는 것입니다. 거의 대부분 데이터의 ID를 키로 사용합니다. 

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
  	{todo.text}
  </li>
);

랜더링 될 아이템의 안정적인 ID를 가질 수 없을 때는, 최후의 방법으로 아이템의 인덱스를 키로 사용할 수도 있습니다. 

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs  
  <li key={index}>
  	{todo.text}
  </li>
);

단 아이템의 순서가 바뀔 수 있다면, 아이템의 인덱스를 ID로 사용하는 것을 추천하진 않습니다.  이것은 성능에 영향을 주며, 컴포넌트 상태 이슈에 원인이 될 수 있기 때문입니다. Robin Pokorny의 '인덱스를 키로 사용했을 때, 네거티브 한 충격에 대한 설명' 기사를 참고해 보십시오. 만약 아이템 리스트에 대한 명시적 할당을 하지 않는다면, 리액트는 기본적으로 인덱스를 키로 설정할 것입니다. 여기 '왜 키가 필요한가에 대한 설명'이 있습니다. 관심 있는 분은 참고해 보세요.

 

 

키로 컴포넌트 추출하기

키는 오직 배열에 둘러싸인 콘텍스트에서만 인식됩니다. 아래의 예를 보면, ListItem 컴포넌트를 추출했을 경우, 배열 안의 <ListItem />앨리먼트에 키를 부여해야 합니다. <li>앨리먼트에 키를 부여해서는 안됩니다. 

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>
  );
}

아래의 예와 같이 배열 내부에 있는 <ListItem /> 컴포넌트에 키를 부여하는 올바른 모습입니다. 

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>
  );
}

가장 좋은 규칙은, 위와 같이 map() 함수 호출 내부에 앨리먼트가 있을 때는 키를 부여하는 것입니다.

 

 

배열에서 키는 유일해야 한다.

배열 안에서 사용되는 키는 유일해야 합니다. 그러나, 글로벌하게 유일할 필요는 없습니다. 아래의 예는 두 개의 다른 배열을 생성할 때 같은 키를 사용한 예입니다. 

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.'}
];

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Blog posts={posts} />);

위 내용의 결과는 다음과 같습니다. 

sidebar와 content는 서로 다른 배열인데, 같은 id를 키로 사용하고 있습니다. 

키는 리액트에 대한 힌트 역할을 하지만, 컴포넌트로 전달되지는 않습니다. 만약 컴포넌트에 키값과 같은 값이 필요하다면, 다른 이름을 사용하여 명시적으로 전달해야 합니다.  

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

위 예에서 id는 props.id로 읽을 수 있지만, key는 props.key로 읽을 수 없습니다. 

 

 

JSX내에 map() 내포하기

이전의 예에서 우리는 listItems를 분리하여 선언하고, 그것을 JSX내에 포함시켰습니다.

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
  	<ListItem key={number.toString()} value={number} />  
  );  
  
  return (
    <ul>
      {listItems}
    </ul>
  );
}

JSX는 중괄호 내에 어떤 수식이든 허용하기 때문에 map()의 결과를 아래와 같이 인라인 할 수 있습니다. 

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
      	<ListItem key={number.toString()} value={number} />      
      )}    
    </ul>
  );
}

위 두 개의 예는 같은 결과를 나타냅니다. 때로는 코드가 더 명확해집니다. 그러나 이 코드는 혼동을 줄 수 있습니다. 자바스크립트에서와 같이 가독성을 위해 변수로 추출하는 것이 더 나은지 결정하는 것은 사용자의 몫입니다. 유념해야 할 것은 map() 함수가 너무 중첩되면, 컴포넌트로 추출할 때가 되었다는 것입니다.   

반응형