[react] 반복되는 컴포넌트를 효율적으로 보여주자 - map

swanious·2021년 5월 29일
2
post-thumbnail

개발환경

react - v17.0.2

typescript - v4.3.2

map활용 컴포넌트 반복하기

html태그 중 ul, li, ol 태그는 목록 데이터를 그려주는 태그입니다. 데이터가 감당할 수 있는 수준에서는 일일이 위의 태그를 직접 활용하여 반영할 수 있겠지만, 데이터가 많아지면 위와 같은 방식으로는 더이상 렌더링하기 쉽지 않다.

이때 사용할 수 있는 함수가 map 함수인데, DOM요소, 컴포넌트를 반복하고 싶을 때 map 함수를 이용하여 컴포넌트를 쉽게 렌더링 할 수 있다.

예제

  • IterationSample.tsx
import React from 'react';

// map활용 컴포넌트 반복하기
function IterationSample() {
  const names: string[] = ['짜장면', '짬뽕', '탕수육'];

  // map함수로 배열을 새로 생성한 후 return으로 컴포넌트를 반환할 수 있습니다.
  const nameList: JSX.Element[] = names.map((name) => <li>{name}</li>);
  return <ul>{nameList}</ul>;
}

export default IterationSample;

map을 활용하면 수많은 데이터도 위처럼 렌더링할 수 있다.

하지만 위의 코드를 렌더링하고 f12를 눌러 개발자도구의 console을 보면 다음과 같은 에러가 발생함을 볼 수 있다. 에러를 확인해보면,

map함수를 활용하여 컴포넌트나 DOM을 반복적으로 렌더링할 때, unique한 key가 필요합니다.

즉, 각 요소에 고유의 key값을 넣어줘야한다.

근데 왜 key값을 넘겨줘야할까? 이에 대한 답은

리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다. 예로 데이터를 생성, 수정, 삭제할 때 빠르게 원소의 변화를 빠르게 감지할 수 있다고 한다.

컴포넌트에 key값 넣어주기

  • 고유한 id가 있을 때,
articles.map((article) => <li key={article.id}>{article.title}</li>)

예로, 페이스북과 같이 피드(게시물) 배열 데이터를 받아서 렌더링할때, 각 게시물마다 고유한 id값이 존재하므로, 위와 같이 고유한 id값을 각 요소에 key값으로 넘겨주면 해결된다.

  • 실습과 같이 고유한 id가 없을 때,
data.map((value, index)=> <li key={index}>{value}</li>)

하지만 실습처럼 넘겨줄 고유한 id값이 없을 때, map의 callback함수의 인수인 index를 활용하여 key값을 넘겨줄 수 있다. 하지만 index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못한다고 한다. (고유한 값이 없을때만 index를 사용해야한다.)

응용

key에 map의 index를 넣어주는 것이 리렌더링에서 비효율이라고 한다. 이를 위해 데이터를 추가할 때 고유한 id값을 넣어주려면 어떻게 해야할까? 다음과 같이 코드를 수정해보자.

초기 상태 추가하기

  1. menus 객체 배열에 id 추가
  2. 객체가 생성될 때 넣어줄 uniqueId, 데이터를 추가하기 위한 inputText 생성
  3. li 태그에 menu.id 넣어주기
function IterationSample() {
    
  // 1.
  const [menus, setMenus] = React.useState<Menus[]>([
    { id: 1, name: '짜장면' },
    { id: 2, name: '짬뽕' },
    { id: 3, name: '탕수육' },
  ]);
    
  // 2.
  const [uniqueId, setUniqieId] = React.useState<number>(4);
    
  // 3.
  const [inputText, setInputText] = React.useState<string>('');
    
  // 4.
  const nameList: JSX.Element[] = menus.map((menu) => <li key={menu.id}>{menu.name}</li>);

  return (
    <>
      <h1>map으로 컴포넌트 반복하기</h1>
      <input type="text" onChange={handleChange} />
      <button type="button" onKeyPress={handlePress} onClick={handleClick}>
        추가
      </button>
      <ul>{nameList}</ul>
    </>
  );
    
}

데이터 추가 기능

위 코드에서 함수만 아래의 함수만 추가한다.

// ...

// text 값을 저장
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
      setInputText(e.target.value);
  };

// 추가 버튼 클릭 시 `li`에 추가
const handleClick = (): void => {
    setMenus(
        menus.concat({
            id: uniqueId,
            name: inputText,
        }),
    );
    setUniqieId(uniqueId + 1);
    setInputText('');
};

// input에 데이터 작성 후 Enter 누르면 `li`에 추가
const handlePress = (e: React.KeyboardEvent<HTMLButtonElement>) => {
    if (e.key === 'Enter') {
        handleClick();
    }
};

//...
  • 클릭/enter 이벤트 전

  • 아래처럼 잘 반영되고, F12의 콘솔에 더이상 오류가 나타나지 않는다.

전체 코드

import React, { ChangeEvent } from 'react';

interface Menus {
  id: number;
  name: string;
}

// map활용 컴포넌트 반복하기
function IterationSample() {
  const [menus, setMenus] = React.useState<Menus[]>([
    { id: 1, name: '짜장면' },
    { id: 2, name: '짬뽕' },
    { id: 3, name: '탕수육' },
  ]);
  const [uniqueId, setUniqieId] = React.useState<number>(4);
  const [inputText, setInputText] = React.useState<string>('');

  const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
    setInputText(e.target.value);
  };

  const handleClick = (): void => {
    setMenus(
      menus.concat({
        id: uniqueId,
        name: inputText,
      }),
    );
    setUniqieId(uniqueId + 1);
    setInputText('');
  };
  const handlePress = (e: any) => {
    if (e.key === 'Enter') {
      handleClick();
    }
  };
  const nameList: JSX.Element[] = menus.map((menu) => <li key={menu.id}>{menu.name}</li>);
  return (
    <>
      <h1>map으로 컴포넌트 반복하기</h1>
      <input
        type="text"
        value={inputText}
        onKeyPress={(e) => handlePress(e)}
        onChange={handleChange}
      />
      <button type="button" onClick={handleClick}>
        추가
      </button>
      <ul>{nameList}</ul>
    </>
  );
}

export default IterationSample;

요약

  • 반복되는 데이터를 렌더링할 때 map 함수를 활용할 수 있다.
  • 컴포넌트 배열을 렌더링할 때 key 값 설정에 주의해야한다. (유일한 값 !)
  • 상태(state)안에서 배열을 변형할 때 배열에 직접 접근하지 않고, concat, filter 로 새로운 배열을 할당하여 새로운 상태로 설정해야한다.
profile
TIL 기록을 위한 블로그

0개의 댓글