관리 메뉴

거니의 velog

(16) 컴포넌트 반복 2 본문

React_리액트 시작

(16) 컴포넌트 반복 2

Unlimited00 2023. 12. 1. 15:53

4. 응용

* 지금까지 배운 개념을 응용하여 고정된 배열을 리렌더링하는 것이 아닌, 동적인 배열을 렌더링하는 것을 구현해 보자. 그리고 index 값을 key로 사용하면 리렌더링이 비효율적이라고 배웠는데, 이러한 상황에서 어떻게 고윳값을 만들 수 있는지도 알아보자. 이 절의 실습 흐름은 다음과 같다.


(1) 초기 상태 설정하기

* IterationSample 컴포넌트에서 useState를 사용하여 상태를 설정해 보자. 세 가지 상태를 사용할 텐데 하나는 데이터 배열이고, 다른 하나는 텍스트를 입력할 수 있는 input의 상태이다. 그럼 마지막 하나는 무엇일까? 그것은 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태이다.

* 배열을 설정할 때, 조금 전에는 단순히 문자열로 이루어진 배열을 만들었지만, 이번에는 객체 형태로 이루어진 배열을 만들어 보자. 해당 객체에는 문자열과 고유 id 값이 있다.

* IterationSample 컴포넌트를 다음과 같이 수정해 보자.

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 namesList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return <ul>{namesList}</ul>;
};

export default IterationSample;

* 이번에는 map 함수를 사용할 때 key 값을 index 대신 name.id 값으로 지정해 주었다.

* 코드를 저장하고, 브라우저에서 이전과 같은 결과가 나타나는지 확인해 보자.


(2) 데이터 추가 기능 구현하기

* 이제 새로운 이름을 등록할 수 있는 기능을 구현해 보자.

* 우선 ul 태그의 상단에 input과 button을 렌더링하고, input의 상태를 관리해 보자.

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 namesList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button>추가</button>
      <ul>{namesList}</ul>
    </>
  );
};

export default IterationSample;

* 그 다음에는 버튼을 클릭했을 때 호출할 onClick 함수를 선언하여 버튼의 onClick 이벤트로 설정해 보자.

* onClick 함수에서는 배열의 내장 함수 concat을 사용하여 새로운 항목을 추가한 배열을 만들고, setNames를 통해 상태를 업데이트해 준다.

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 onClick = () => {
    const nextNames = names.concat({
      id: nextId, // nextId 값을 id로 설정하고
      text: inputText,
    });
    setNextId(nextId + 1); // nextId 값에 1을 더해 준다.
    setNames(nextNames); // names 값을 업데이트한다.
    setInputText(''); // inputText를 비운다.
  };

  const namesList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      <ul>{namesList}</ul>
    </>
  );
};

export default IterationSample;

* 배열에 새 항목을 추가할 때 배열의 push 함수를 사용하지 않고 concat을 사용했는데, push 함수는 기존 배열 자체를 변경해 주는 반면, concat은 새로운 배열을 만들어 준다는 차이점이 있다.

* 리액트에서 상태를 업데이트할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 한다. 이를 불변성 유지라고 하는데, 불변성 유지를 해 주어야 나중에 리액트 컴포넌트의 성능을 최적화할 수 있다. 이에 대한 자세한 내용은 추후에 알아보도록 하자.

* onClick 함수에서 새로운 항목을 추가할 때 객체의 id 값은 nextId를 사용하도록 하고 클릭될 때마다 값이 1씩 올라가도록 구현했다. 추가로 button이 클릭될 때 기존의 input 내용을 비우는 것도 구현해 주었다.

* 코드를 다 작성했다면 파일을 저장하고 브라우저를 열어서 새로운 항목을 추가해 보자.


(3) 데이터 제거 기능 구현하기

* 이번에는 각 항목을 더블클릭했을 때 해당 항목이 화면에서 사라지는 기능을 구현해 보자. 이번에도 마찬가지로 불변성을 유지하면서 업데이트 해 주어야 한다. 불변성을 유지하면서 배열의 특정 항목을 지울 때는 배열의 내장 함수 filter를 사용한다.

* filter 함수를 사용하면 배열의 특정 조건을 만족하는 원소들만 쉽게 분류할 수 있다. 사용 예시를 한 번 보자.

const numbers = [1, 2, 3, 4, 5, 6];
const biggerThanThree = numbers.filter(number => number > 3);
console.log(biggerThanThree);

* filter 함수의 인자에 분류하고 싶은 조건을 반환하는 함수를 넣어주면 쉽게 분류할 수 있다.

* 이 filter 함수를 응용하여 특정 배열에서 특정 원소만 제외시킬 수도 있다. 예를 들어 위 코드에서 본 numbers 배열에서 3만 없애고 싶다면 다음과 같이 하면 된다.

* 이제 filter 함수를 사용하여 IterationSample 컴포넌트의 항목 제거 기능을 구현해 보자. HTML 요소를 더블클릭할 때 사용하는 이벤트 이름은 onDoubleClick이다. onRemove라는 함수를 만들어서 각 li 요소에 이벤트 등록을 해 주자.

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 onClick = () => {
    const nextNames = names.concat({
      id: nextId, // nextId 값을 id로 설정하고
      text: inputText,
    });
    setNextId(nextId + 1); // nextId 값에 1을 더해 준다.
    setNames(nextNames); // names 값을 업데이트한다.
    setInputText(''); // inputText를 비운다.
  };

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

  const namesList = names.map((name) => (
    <li key={name.id} onDoubleClick={() => onRemove(name.id)}>
      {name.text}
    </li>
  ));
  return (
    <>
      <input value={inputText} onChange={onChange} />
      <button onClick={onClick}>추가</button>
      <ul>{namesList}</ul>
    </>
  );
};

export default IterationSample;

* 코드를 저장하고 브라우저에서 항목을 더블클릭하여 제거해 보자. 잘 지워지는가?


5. 정리

* 이 장에서는 반복되는 데이터를 렌더링하는 방법을 배우고, 이를 응용하여 유동적인 배열을 다루어 보았다. 컴포넌트 배열을 렌더링할 때는 key 값 설정에 항상 주의해야 한다. 또 key 값은 언제나 유일해야 한다. key 값이 중복된다면 렌더링 과정에서 오류가 발생한다.

* 상태 안에서 배열을 변형할 때는 배열에 직접 접근하여 수정하는 것이 아니라 concat, filter 등의 배열 내장 함수를 사용하여 새로운 배열을 만든 후 이를 새로운 상태로 설정해 주어야 한다는 것을 명심하자. 우리는 더욱 다양한 배열 및 객체 업데이트 방법들을 계속 배워 나갈 것이다.