IT/React

리액트(React) 스터디일지7:반복문(Map)

라임웨일 2021. 4. 8. 15:44
반응형

 

리액트에 반복문을 사용할 때 Map함수를 사용합니다.

Map함수를 조금 더 자세히 알고 싶으시면 Mozilla에서 제공하는 Map Docs를 살펴보거나 이전에 제가 작성한 Map 매서드 이해하기 글을 한번 읽어보시면 조금 더 이해하기 쉬울거에요.  같이 읽어보면 조금 더 도움이 되는 내용으로는 전개연산자filter 매서드 도 같이 한번 읽어 보시면 앞으로 우리가 React에서 반복문을 사용할 때 많은 도움이 될거라 생각합니다.

 

 

우선 오늘의 주인공인 Map에 대해서 간략히 살펴보겠습니다.

 

arr.map( callback, [thisArg])

callback : 새로운 배열의 요소를 생성하는 함수.

  • currentValue :  처리할 현재 요소
  • index : 처리할 현재 요소의 인덱스
  • array : 현재 처리하고 있는 요소의 원본 배열

thisArg(선택항목) : callback 함수 내부에서 사용할 this 래퍼런스

 

 

map은 callback 함수를 각각의 요소에 대해 한번씩 순서대로 불러 그 함수의 반환값으로 새로운 배열을 만듭니다. callback 함수는 (undefined도 포함해서) 배열 값이 들어있는 인덱스에 대해서만 호출됩니다. 즉, 값이 삭제되거나 아직 값이 할당/정의되지 않은 인덱스에 대해서는 호출되지 않습니다.

callback 함수는 호출될 때 대상 요소의 값, 그 요소의 인덱스, 그리고 map을 호출한 원본 배열 3개의 인수를 전달받습니다.

thisArg 매개변수가 map에 전달된 경우 callback 함수의 this값으로 사용됩니다. 그 외의 경우undefined값이 this 값으로 사용됩니다. callback 함수에서 최종적으로 볼 수 있는 this 값은 함수 내 this를 정하는 일반적인 규칙에 따라 결정됩니다.

map은 호출한 배열의 값을 변형하지 않습니다. 단, callback 함수에 의해서 변형될 수는 있습니다.

map이  처리할 요소의 범위는 첫 callback을 호출하기 전에 정해집니다. map이 시작한 이후 배열에 추가되는 요소들은 callback을 호출하지 않습니다. 배열에 존재하는 요소들의 값이 바뀐 경우 map이 방문하는 시점의 값이 callback에 전달됩니다. map이 시작되고, 방문하기 전에 삭제된 요소들은 방문하지 않습니다.

명세서에 정의된 알고리즘으로 인해 map을 호출한 배열의 중간이 비어있는 경우, 결과 배열 또한 동일한 인덱스를 빈 값으로 유지합니다.

 

import React from 'react';

const IterationSample = () => {
  const names = ['일', '이', '삼', '사'];
  const nameList = names.map((nameElement) => <li>{nameElement}</li>);

  return <ul>{nameList}</ul>;
};

export default IterationSample;

위처럼 코드를 작성하고 실행하면 값은 출력되지만 콘솔창에

 

index.js:1 Warning: Each child in a list should have a unique "key" prop.

Check the render method of `IterationSample`. See https://reactjs.org/link/warning-keys for more information.

 

위와 같은 에러가 나타나는것을 볼 수 있습니다. key값이 없다고 오류가 생기는 건데요. 리액트에서 Key는 컴포넌트 배열을 랜더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용합니다. 그렇기 때문에 Key값은 언제나 유일한 값이어야 합니다. 

 

import React from 'react';

const IterationSample = () => {
  const names = ['일', '이', '삼', '사'];
  const nameList = names.map((nameElement, i) => (
    <li key={i}>{nameElement}</li>
  ));

  return <ul>{nameList}</ul>;
};

export default IterationSample;

인덱스를 key값으로 넣어서 해결. 하지만 index를 키값으로 사용하는 방법은 리랜더링이 비효율적입니다. 인덱스를 사용하게 되면 단순한 데이터 조회는 크게 문제되지 않지만 값을 수정, 추가, 삭제시 인덱스 값이 변하기 때문에 좋은 방법이라 할 수 없습니다.

 

import React, { useState } from 'react';

const IterationSample = () => {
  const [names, setNames] = useState([
    { id: 1, text: '일' },
    { id: 2, text: '이' },
    { id: 3, text: '삼' },
    { id: 4, text: '사' },
  ]);
  const nameList = names.map((nameElement, i) => (
    <li key={nameElement.id}>{nameElement.text}</li>
  ));

  return <ul>{nameList}</ul>;
};

export default IterationSample;

아이디를 사용하여 불변성을 높여주었습니다. 

 

데이터가 추가되는 예제를 한번 살펴볼게요.

mport React, { useState } from 'react';

const IterationSample = () => {
  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 = (e) => {
    const copyItem = names.concat({
      id: nextId,
      text: inputText,
    });

    setNextId(nextId + 1);
    setNames(copyItem);
    setInputText('');
  };

  const nameList = names.map((nameElement, i) => (
    <li key={nameElement.id}>{nameElement.text}</li>
  ));

  return (
    <div>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      <ul>{nameList}</ul>
    </div>
  );
};

export default IterationSample;

배열에 새 항목을 추가할 때는 push 함수를 사용하지 않고 concat을 사용합니다. push는 기존 배열 자체를 변경하기 때문에 값이 변경되어도 리액트는 값이 정말 변경되었는지 인지하지를 못합니다. concat은 기존 배열의 값은 수정하지 않고 새로운 배열을 만들어서 해당 값만을 변경하기 때문에 리액트가 값이 변경되었음을 인지할 수 있습니다. 

이 내용은 얕은 비교, 불변성과 관련된 내용입니다. 

 

관심이 있으신 분들은 한번 찾아보는것도 좋을것 같습니다. 해당 내용은 기회가 되면 따로 한번 정리를 하도록 하겠습니다.

 

아래 코드는  filter 함수까지 사용한 최종 코드 입니다. 

import React, { useState } from 'react';

const IterationSample = () => {
  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 onClickAdd = (e) => {
    const copyItem = names.concat({
      id: nextId,
      text: inputText,
    });

    setNextId(nextId + 1);
    setNames(copyItem);
    setInputText('');
  };

  const onClickRemove = (id) => {
    const copyItem = names.filter((nameTg) => nameTg.id !== id);
    setNames(copyItem);
  };

  const nameList = names.map((nameElement) => (
    <li key={nameElement.id}>
      {nameElement.text}
      <button onClick={() => onClickRemove(nameElement.id)}>삭제</button>
    </li>
  ));

  return (
    <div>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClickAdd}>추가</button>
      <ul>{nameList}</ul>
    </div>
  );
};

export default IterationSample;

 

반응형