React 공식문서 정독하기 - 2. 리스트와 key

kind J·2022년 6월 6일
0

React 공식문서

목록 보기
2/3
post-thumbnail

리스트와 key

엘리먼트 모음을 만들고 중괄호 {}를 이용해서 JSX에 포함시킬 수 있다.

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

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

일반적으로 컴포넌트 안에서 리스트를 랜더링한다.

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

이 코드를 실행하면 리스트의 각 항목에 key 를 넣어야 한다는 경고가 표시된다.
"key" 는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이다.

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

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

Key

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

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

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

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

렌더링 한 항목에 안정적인 아이디가 없다면 최후의 수단으로 항목의 index 를 사용할 수 있다.

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

항목의 순서가 바뀔 수 있는 경우 key 인덱스를 사용하는 것을 권장하지 않는다.
성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있다.
리스트 항목에 명시적으로 key 를 지정하지 않으면 React 는 기본적으로 인덱스를 key 로 사용한다.

자식에 대한 재귀적 처리

DOM 노드의 자식들을 재귀적으로 처리할 때 리액트는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.

자식 끝에 엘리먼트를 추가하면 두 트리 사이의 변경은 잘 작동한다.

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React 는 두 트리에서

 <li>first</li>

가 일치하는 것을 확인하고 이어서

 <li>second</li>

가 일치하는것을 확인한다.

그리고 마지막으로

<li>third</li>

를 트리에 추가한다.

하지만 위와 같이 단순하게 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지 않다. 예를 들어, 아래의 두 트리 변환은 형편없이 작동한다.

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

이러한 문제를 해결하기 위해 React 는 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>

이제 React는 '2014' key를 가진 엘리먼트가 새로 추가되었고, '2015'와 '2016' key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.

key는 반드시 변하지 않고, 예상 가능하며, 유일해야 합니다. 변하는 key(Math.random()으로 생성된 값 등)를 사용하면 많은 컴포넌트 인스턴스와 DOM 노드를 불필요하게 재생성하여 성능이 나빠지거나 자식 컴포넌트의 state가 유실될 수 있다.

key로 컴포넌트 추출하기

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

예를 들어 ListItem 컴포넌트를 추출한 경우 ListIte 안에 있는 li 엘리 먼트가 아니라 배열의 <ListItem / > 엘리먼트가 키를 가져야 한다.

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

Key는 형제 사이에서만 고유한 값이어야 한다.

React에서 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() 포함시키기

별도의 listItems 변수를 선언하고 이를 JSX 에 포함한다.

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

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

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

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

profile
프론트앤드 개발자로 일하고 있는 kind J 입니다.

0개의 댓글