끄적끄적 코딩
Published 2023. 6. 22. 06:00
[React] 컴포넌트 반복 React

컴포넌트 반복이 필요한 이유

const IterationSample = () => {
	return (
		<ul>
			<li>눈사람</li>
			<li>얼음</li>
			<li>눈</li>
			<li>바람</li>
		</ul>
	);
};

위와 같이 <li> 태그가 반복적으로 나타나는 것은 문제가 되지 않는 것 처럼 보이지만 코드가 더 복잡해진다면 코드양은 더 늘어나고 파일 용량도 쓸데없이 증가하고 보여 주어야 할 데이터가 유동적이라면 직접 내용을 바꿔 주는 것은 한계가 있기 때문에 저러한 코드로는 절대 관리하지 못합니다.

따라서 자바스크립트 배열의 map() 함수를 통해 반복적인 내용을 효율적으로 보여주고 관리해야 함.

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

map 함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성합니다.

문법

arr.map(callback(currenValue, index, array), [thisArg])

callback

  • 새로운 배열의 요소를 생성하는 함수. 각 요소가 callback 함수를 거쳐 나온 결과값이 새로운 배열의 요소가 됨.
  • 파라미터 : 반드시 모두 사용해야 하는 건 아님.
    • currentValue : 현재 처리하고 있는 요소.
    • index : 현재 처리하고 있는 요소의 index 값.
    • array : 현재 처리하고 있는 원본 배열.

thisArg(선택 항목)

  • callback 함수 내부에서 사용할 this 래퍼런스.
  • this는 동적으로 바인딩되므로 map()의 콜백 내의 this는 기본적으로 undefined로 바인딩된다고 함.
  • → 따라서 map()의 두 번째 인자인 thisArg를 사용하여 콜백의 this로 바인딩될 객체를 전달.

 

key

key 란 그 값이 변하지 않는, 유일한(형제 사이에서만) 식별자 역할로 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕고 엘리먼트에 안정적인 고유성을 부여함.

key를 사용해야 하는 이유

- key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용.
- key가 없을 때는 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지.
- key가 있을 때는 이 값을 사용하여 어떤 변화가 일어났는지 더욱 빠르게 알아냄.

* key가 있을 때, 없을 때위와 같이 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>

key를 비교하여 같은 key를 가진 컴포넌트가 있다면 그 컴포넌트가 사라지지 않고 이동만 하였다는 것을 인식할 수 있어서 제거하고 다시 그리지 않음.

  • 따라서 key를 통해 DOM의 변경사항을 확인하는 휴리스틱한 O(n^3)의 시간이 걸리던 비교 알고리즘을 **O(n)**으로 줄일 수 있음.
  • 하지만 key가 있다면
  • // 기존 트리 <ul> <li>Duke</li> <li>Villanova</li> </ul> // 새로운 트리 <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul>


key 설정

- key를 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정.
- key값은 언제나 유일해야 하므로 데이터가 가진 고윳값으로 설정하는 것이 좋음.
- 만일 고유값이 없다면 아래와 같이 map()의 callback 함수의 파라미터인 index를 사용해도 됨.
- 리스트 항목에서 명시적으로 key를 지정하지 않으면 React는 기본적으로 index를 key로 사용.

const IterationSample = () => {
  const names = ["눈사람", "얼음", "눈", "바람"];
  const nameList = names.map((name, index) => <li key={index}>{name}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

하지만 고유한 값이 없을 때만 index를 key로 이용하고 이외의 경우엔 index를 key로 이용하는 것을 권장하지 않음.

* index를 key로 사용하면 안 좋은 이유

// 변경 전
<ul>
  <li key="1">Duke</li>
  <li key="2">Villanova</li>
</ul>

// 변경 후
<ul>
  <li key="1">Connecticut</li>
  <li key="2">Duke</li>
  <li key="3">Villanova</li>
</ul>

위 처럼 기존 컴포넌트 앞에 새로운 컴포넌트가 추가되면 index는 순차적으로 맵핑이로 인해 index를 key로 사용하면 항목의 순서가 바뀌었을 때, 중간이나 앞에 새로운 값이 추가되었을 때 의도치 않은 방향으로 바뀔 수 있음.

→ key를 통해 비교하면 새롭게 추가된 컴포넌트가 기존의 컴포넌트와 같다고 인식되어 기존 컴포넌트를 그대로 그림.
index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못함.


초기 상태 설정하기

import { 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(""); // 텍스트를 입력할 수 있는 input의 상태
  const [nextId, setNextId] = useState(5); // 새로운 항목을 추가할 때 사용할 id(key)

  const nameList = names.map((name) => <li key={name.id}>{name.text}</li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

index를 key로 사용하지 않고 id라는 고윳값을 key로 사용.


데이터 추가 기능 구현하기

배열에 새 항목을 추가할 때, concat() 사용.

→ 리액트에서는 상태를 업데이트할 때 기존 상태를 그대로 두면서 새로운 값을 상태로 설정하는 불변성 유지를 해주어야 함. 따라서 새로운 배열을 생성하는 concat을 사용한 것.

import { 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(""); // 텍스트를 입력할 수 있는 input의 상태
  const [nextId, setNextId] = useState(5); // 새로운 항목을 추가할 때 사용할 id(key)

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

  const onClick = () => {
    const nextNames = names.concat({
      id: nextId,
      text: inputText,
    });
    setNextId(nextId + 1); // nextId + 1을 해주어 다음 항목에 사용할 id 업데이트
    setNames(nextNames); // names 값 업데이트
    setInputText(""); // inputText를 비움
  };

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

export default IterationSample;
  • onChange : 입력을 할 때마다 input의 상태 관리.
  • onClick : 배열을 연결하는 내장 함수인 concat을 통해 새로운 항목을 추가한 배열로 names를 업데이트.


데이터 제거 기능 구현하기

불변성을 유지하면서 배열의 특정 항목을 지워야 함.
배열 내장 함수 filter() 사용.

filter()

- 배열에서 특정 조건을 만족하는 원소들만 분류.
- 인자에 분류하고 싶은 조건을 반환하는 함수를 넣어줌.
- 예시

cosnt numbers = [1, 2, 3, 4, 5, 6];
const withoutThree = numbers.filter(number => number !== 3);
// 결과: [1, 2, 4, 5, 6]

데이터 제거 기능

import { 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(""); // 텍스트를 입력할 수 있는 input의 상태
  const [nextId, setNextId] = useState(5); // 새로운 항목을 추가할 때 사용할 id(key)

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

  const onClick = () => {
    const nextNames = names.concat({
      id: nextId,
      text: inputText,
    });
    setNextId(nextId + 1); // nextId + 1을 해주어 다음 항목에 사용할 id 업데이트
    setNames(nextNames); // names 값 업데이트
    setInputText(""); // inputText를 비움
  };

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

  const nameList = 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>{nameList}</ul>
    </>
  );
};

export default IterationSample; 

* 왜 onDoubleClick={onRemove(name.id)}라 쓰면 안될까?

위와 같이 onRemove(name.id)는 함수가 아닌 실행값을 넣어준 것따라서 콜백 함수에 인자를 넣어주고 싶을 때는 화살표 함수를 이용하거나 바인딩하여 넘겨줘야 함.

→ 이벤트 발생 여부와 상관없이 페이지가 실행되자마자 호출됨.

콜백 함수에 어떤 인자를 넘겨줘야 할 때는 함수를 호출 하는 것이 아닌 함수를 선언 해줘야 함.

'React' 카테고리의 다른 글

[React] Hooks  (0) 2023.06.22
[React] 컴포넌트의 라이프사이클 메서드  (0) 2023.06.22
[React] ref란  (0) 2023.06.20
[React] 이벤트 핸들링  (0) 2023.06.20
[React] 컴포넌트  (0) 2023.06.20

검색 태그