React를 다루는 기술 07 - 컴포넌트 반복

3 minute read

import React from 'react';

const sample = () => {
  return (
    <ul>
      <li></li>
      <li>눈사람</li>
      <li>얼음</li>
      <li></li>
      <li>눈물</li>
    </ul>
  )
}

위의 코드는 개발을 하다보면 흔하게 볼 수 있는 리스트이다.
보통 이러한 리스트 혹은 게시판 페이지들은 하나의 DOM요소가 반복되는 경우가 대부분인데 위 코드에선 <li></li>가 반복되고 있다.
이러한 경우 위처럼 하나하나 적어주는 것으로도 구현이 가능하지만 리스트의 길이가 수백개로 늘어날 경우 하나하나 작성하고 수정하기 번거롭고 관리도 어려운 좋지 않은 형태이다.
이처럼 반복되는 형태의 DOM요소를 React에서 간단하게 구현하기 위한 반복되는 컴포넌트를 렌더링 할 수 있는 방법을 알아보자.

자바스크립트의 map() 함수

map() 함수는 자바스크립트에 내장된 함수로 배열을 반복하여 배열 내의 각 요소들에 원하는 규칙을 적용하여 새로운 배열을 생성하는 함수이다.

arr.map(callback, [thisArg]);
  • callback : 새로운 배열의 요소를 생성하는 함수로 3가지 파라미터를 가진다.
    • currentValue : 현재 처리하고 있는 요소
    • index : 현재 처리하고 있는 요소의 index값
    • array : 현재 처리하고 있는 원본 배열
  • thisArg(선택항목) : callback 함수 내부에서 사용할 this 레퍼런스

위의 함수를 이용해 배열로 받은 데이터를 컴포넌트의 배열로 반환하여 렌더링하는 방식을 통해 구현하게되며 최상단에서 작성했던 코드를 수정하면 아래와 같다.

import React from 'react';

const sample = () => {
  const names = ['', '눈사람', '얼음', '', '눈물'];
  const nameList = names.map(name => <li>{name}</li>);

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

이처럼 훨씬 간결하고 수정하기도 편한 형태로 완성된다.
그런데 위의 코드를 실행해보면 보기엔 잘 나오지만 콘솔창에선 에러메시지가 표시된다.
이는 map 함수를 통해 반복하여 생성하는 과정에서 key속성을 빼먹었기 때문이다.

key

React에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 요소에 변화가 있었는지 알아내기 위해 사용된다.
가령 유동적으로 추가되거나 삭제되거나 변경되는 컴포넌트 배열을 사용할 경우 해당 배열의 컴포넌트 중 어떤 것이 적용되는지 확인하기 위해 React는 배열을 처음부터 쭉 비교해보게 되는데 key값이 있을 경우 그럴 필요없이 어느 부분이 업데이트되는지 빠르게 파악할 수 있다.
이를 통해 업데이트가 발생할 경우 보다 빠르고 적은 자원으로 처리가 가능하다.

key를 설정하는 방법은 map함수를 통해 반복하는 과정에서 추가해주는 방법을 사용한다.

import React from 'react';

const sample = () => {
  const names = ['', '눈사람', '얼음', '', '눈물'];
  const nameList = names.map((name, index) => {<li key={index}>{name}</li>);

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

key는 해당 컴포넌트 내에서 key 중에서 유니크한 값이어야하기 때문에 map함수를 이용할 때 생성되는 index를 이용하여 적용하였다.
하지만 본래 key값에는 iterator함수의 index값을 사용해서는 안되는데 이 index값을 얻어내기 위해서는 결국 배열을 돌릴 필요가 있고 때문에 리렌더링의 효율이 나빠지기 때문이다.
때문에 보통은 배열자체에 유니크한 id값이 있거나 외부에서 따로 생성하여 적용하는 방법을 사용한다.

컴포넌트 추가 및 삭제기능 구현하기

import React, {useState} from 'react';

const sample = () => {
  const [names, setNames] = useState({
    {id: 1, text: '눈사람'},
    {id: 2, text: ''},
    {id: 3, text: '사람'},
    {id: 4, text: '바람'}
  });
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); // 새로운 항목이 추가될 때 이 id값을 사용한다.

  const onChange = e => setInputText(e.target.value);
  const namesList = names.map(name => <li key={name.id}>{name.text}</li>);

  const onClick = () => {
    const nextNames = names.concat(() => {
      id: nextId,
      text: inputText
    });
    
    setNextId(nextId + 1);
    setNames(nextNames);
    setInputText('');
  }

  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      {namesList}
    <>
  )
}

위의 코드는 names state에 담긴 배열을 반복하여 리스트를 표시하고
input에 값을 입력하고 버튼을 클릭하면 해당 내용을 리스트에 추가해주는 코드이다.

여기서 중요한 부분이 names state에 배열을 더해주기 위해 사용한 concat함수인데 이 함수의 특징은 기존 함수에 입력한 값을 더해주면서 새로운 배열을 생성한다는 점이다.
통상 배열에 새로운 값을 추가해줄 때 사용하는 함수는 push()이지만 이 함수는 기존의 배열을 수정한다는 점에서 concat함수와 차이가 있다.
React에서는 상태를 업데이트 할 때 기존의 값을 유지한 상태로 새로운 값을 생성하여 교체해줘야하는데 이를 불변성 유지라고 한다.
React의 효율을 최적화하기 위해 꼭 지켜야하기 때문에 배열에 값을 추가할 때 concat 함수를 사용하게된다.

데이터 제거기능 구현하기

import React, {useState} from 'react';

const sample = () => {
  const [names, setNames] = useState({
    {id: 1, text: '눈사람'},
    {id: 2, text: ''},
    {id: 3, text: '사람'},
    {id: 4, text: '바람'}
  });
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); // 새로운 항목이 추가될 때 이 id값을 사용한다.

  const onChange = e => setInputText(e.target.value);

  const onClick = () => {
    const nextNames = names.concat(() => {
      id: nextId,
      text: inputText
    });
    
    setNextId(nextId + 1);
    setNames(nextNames);
    setInputText('');
  }

  const onRemove = (id) => {
    const nextNames = names.filter(name => name.id !== id);
    setNames(nextNames);
  }

  const namesList = names.map(name => {
                                  return (
                                    <li key={name.id} 
                                        onDoubleClick={() => onRemove(name.id)}>{name.text}</li>
                                  )
                                });

  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      {namesList}
    <>
  )
}

리스트 항목을 더블클릭하면 해당 항목을 제거하는 코드를 추가하였다.
더블클릭 이벤트를 트리거하기 위해 onDoubleClick함수를 리스트에 추가하고 콜백함수로 onRemove함수에 파라미터로 id값을 넣어주었다.
요소를 제거하기 위해 사용한 함수는 filter로 조건을 부여하여 해당 조건에 맞는 요소만 남긴 새 배열을 반환하는 함수이다.
이를 통해 더블클릭한 요소의 id를 가진 항목을 제외한 나머지만을 담은 배열을 반환하고 state를 업데이트하는 방식으로 구현되었다.

출처자료

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=209354690 (리액트를 다루는 기술(개정판))

Categories:

Updated: