[React] 리액트와 리스트

mokyoungg·2020년 5월 18일
0

출처 : 리액트 공식 홈페이지
https://ko.reactjs.org/docs/lists-and-keys.html

여러개의 컴포넌트 렌더링 하기

엘리먼트 모음을 만들고 중괄호 {}를 이용하여 JSX에 포함시킬 수 있다.
아래의 JavaScript map() 함수를 사용하여 numbers 배열을 반복 실행한다.
각 항목에 대해 li태그 엘리먼트르 반환하고 엘리먼트 배열의 결과를 listItems에 저장한다.

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

listItems 배열을 ul 태그 엘리먼트 안에 포함하고 DOM에 렌더링 합니다.

ReactDOM.render(
<ul>{listItems}</ul>, document.getElementById('root'));


기본 리스트 컴포넌트

일반적으로 컴포넌트 안에서 리스트를 렌더링한다.
이전 예제를 numbers 배열을 받아서 순서 없는 앨리먼트 리스트를 출력하는 컴포넌트로 리팩토링할 수 있다.

functoin 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'는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이다.
number.map() 안에서 리스트의 각 항목에 key를 할당하여 키 누락 문제를 해결하자.

functoin NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) => 
    <li key={number.toString()}> //각 항목에 key를 할당.
      {number}
    </li>);

  return (
    <ul>{listItems}</ul>
  )
}

const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);


key

key는 리액트가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다.
key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야한다.

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

key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을
고유하게 식별할 수 있는 문자열을 사용하는 것이다.
대부분의 경우 데이터의 ID를 key로 사용한다.

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

렌더링 한 항목에 대한 안정적인 ID가 없다면
최후의 수단으로 항목의 인덱스를 key로 사용할 수 있다.

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

항목의 순서가 바뀔 수있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다.
이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있다.
참고 : https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

만약 리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용한다.

왜 key가 필요한가 : https://ko.reactjs.org/docs/reconciliation.html#recursing-on-children
리액트는 key 속성을 지원하는데, 자식들이 key를 가지고 있다면.
key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.
예를 들어, key를 추가하여 트리의 변환 작업이 효율적으로 수행되도록 수정할 수 있다.

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

리액트는 '2014' key를 가진 엘리먼트가 새로 추가되었고,
'2015' , '2016' key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.
(참고_리액트 가상 DOM : https://www.codecademy.com/articles/react-virtual-dom)

실제로 key로 사용할 값을 정하는 것은 어렵지 않다. 그리려고 하는 엘리먼트는 일반적으로
식별자를 가지고 있을 것이고, 그대로 해당 데이터를 key로 사용할 수 있다.

<li key={items.id}>{item.name}</li>

만약 이러한 상황에 해당하지 않는다면, 여러분의 데이터 구조에 ID라는 속성을 추가해주거나
데이터 일부에 해시를 적용해서 key를 생성할 수 있다.
해당 key는 오로지 형제 사이에서만 유일하면 되고, 전역에서 유일할 필요는 없다.

최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다.
만약 항목들이 재배열되지 않는다면 이 방법도 잘 도작할 것이지만, 재배열되는 경우
비효율적으로 동작할 것이다.

인덱스를 key로 사용 중 배열이 재배열되면 컴포넌트의 state와 관련된 문제가 발생할 수 있다.
컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용된다. 인덱스를 key로 사용하면,
항목의 순서가 바뀌었을 때 key 또한 바뀔 것이다. 그 결과로, 컴포넌트의 state가 엉망이 되거나
의도하지 않은 방식으로 바뀔 수 있다.


key로 컴포넌트 추출하기

키는 주변 배열의 context에서만 의미가 있다.

예를 들어 ListItem 컴포넌트를 추출한 경우, ListItem 안에 있는 li 태그 엘리먼트가 아니라
배열의 ListItem 컴포넌트의 엘리먼트가 key를 가져야 한다.

무슨말이지.. 컴포넌트가 리스트를 나타낼 때, 컴포넌트 안에 있는 리스트 태그들이 키를 가져야한다는 말인가. 아니면 컴포넌트가 컴포넌트 사이에서 구분될 수 있게 키를 가져야한다는 말인가. 아마도 전자 이야기인듯?

예시: 잘못된 key 사용법

function ListItem(props) {
  const value = props.value;
  return (
    // 틀렸습니다! 여기에는 key를 지정할 필요가 없습니다~
    <li key={value.toString()}>
      {value}
    </li>
   );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 여기에 key를 지정해야한다.
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

예시 : 올바른 key 사용법

function ListItem(props) {
  //맞습니다. 여기에는 key를 지정할 필요가 없습니다.
  return <li>{props.value}</li>
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    //맞습니다. 배열 안에 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')
);

map() 함수 내부에 있는 엘리먼트에 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')
);

리액트에서 key는 힌트를 제공하지만 컴포넌트로 전달하지는 않습니다.
컴포넌트에서 key와 동일한 값이 필요하면 다른 이름의 prop으로 명시적으로 전달합니다.

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

Post 컴포넌트는 props.id를 읽을 수 있지만 props.key는 읽을 수 없다.


JSX에 map() 포함시키기

JSX를 사용하면 중괄호 안에 모든 표현식을 포함시킬 수 있으므로
map() 함수의 결과를 인라인으로 처리할 수 있습니다.

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

이 방식을 사용하면 코드가 더 깔끔해지지만, 이 방식을 남발하는 것은 좋지 않다.
자바스크립트와 마찬가지로 가독성을 위해 변수로 추출해야 할지 아니면 인라인으로
넣을지는 개발자가 직접 판단해야 한다.
map() 함수가 너무 중첩된다면 컴포넌트로 추출하는 것이 좋다.


어려워어려워

profile
생경하다.

0개의 댓글