본문 바로가기
JavaScript/React

React) 컴포넌트 반복 (map, filter, key)

by 박채니 2022. 11. 14.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

 

컴포넌트 반복

 

웹 페이지를 만들다보면, 아래와 같이 반복되는 코드를 작성할 때가 있습니다.

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

export default IterationSample;

만일 코드가 복잡해지고 프로젝트 크기가 방대해진다면, 이는 파일 용량을 낭비 혹은 관리가 어려울 것입니다.

이 때 반복을 통해 중복을 없애줄 수 있습니다.

 


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

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

 

문법

 

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

 

예제

 

var numbers = [1, 2, 3, 4, 5];

var processed = numbers.map(function(num) {
    return num* num
});

console.log(processed);

@콘솔출력값
[1, 4, 9, 16, 25]

배열 1, 2, 3, 4, 5의 각 요소를 제곱하여 새로운 배열을 생성한 것을 확인할 수 있습니다.

 

const numbers = [1, 2, 3, 4, 5];
const result = numbers.map((num) => num * num);
console.log(result);

@콘솔출력값
[1, 4, 9, 16, 25]

위처럼 화살표 함수로도 표현할 수 있습니다.

map 함수를 파악했다면, 이제 컴포넌트 반복 처리를 해보겠습니다.


데이터 배열을 컴포넌트 배열로 변환하기

 

컴포넌트 수정하기

 

IterationSample.js

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

export default IterationSample;

문자열로 구성된 배열을 선언 후, map 함수를 이용하여 <li> 태그로 감싸진 JSX 코드로 된 배열을 생성하고 리턴해줍니다.

 

App 컴포넌트에서 예제 컴포넌트 렌더링

 

App.js

import { Component } from 'react';
import './App.css'
import IterationSample from './IterationSample';

class App extends Component {
  render() {
    return (
      <IterationSample/>      
    )
  }
}

export default App;

렌더링이 잘 되고, console에 찍힌 배열도 잘 리턴 되는 것을 확인할 수 있지만, 'key' props가 없다는 경고 메세지가 표시되었습니다.

 


Key

- 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용

- key가 없을 때는 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지하지만, key가 있다면 해당 key 값을 이용해 어떤 변화가 있었는지 빠르게 알아낼 수 있음

 

Key 설정

 

key 값은 반드시 유일해야하므로, 데이터가 가진 고윳값을 지정해줘야 합니다.

하지만, 위에서 생성한 컴포넌트에서는 고유 번호가 별도 없으므로 index를 이용해주겠습니다.

 

IterationSample.js

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

export default IterationSample;

오류 메세지가 없어진 것을 확인할 수 있습니다.

※ 고유한 값이 없을 때만 index 사용!!

index를 key값으로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못함

 


응용

 

초기 상태 설정하기

 

IterationSample.js

import { useState } from 'react';

const IterationSample = () => {
    // 데이터 배열
    const [names, setNames] = useState([
        {id: 1, text: '눈사람'},
        {id: 2, text: '얼음'},
        {id: 3, text: '눈'},
        {id: 4, text: '바람'}
    ]);
    // 텍스트 입력할 input 상태
    const [inputText, setInputText] = useState('');
    // 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태
    const [nextId, setNextId] = useState(5);

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

export default IterationSample;

useState를 이용하여 3가지의 상태를 설정하였습니다.

① 데이터 배열

② 텍스트를 입력할 input 상태

③ 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태

 

데이터 배열에는 id와 text로 이루어져있는 객체를 담고 있는 배열을 만들었습니다.

 

데이터 추가 기능 구현하기

 

IterationSample.js

import { useState } from 'react';

const IterationSample = () => {
    // 데이터 배열
    const [names, setNames] = useState([
        {id: 1, text: '눈사람'},
        {id: 2, text: '얼음'},
        {id: 3, text: '눈'},
        {id: 4, text: '바람'}
    ]);
    // 텍스트 입력할 input 상태
    const [inputText, setInputText] = useState('');
    // 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태
    const [nextId, setNextId] = useState(5);

    // input 박스에 변경이 있을 때마다 inputText set
    const onChange = (e) => setInputText(e.target.value);
    // 추가 버튼 클릭 시
    const onClick = () => {
        const nextNames = names.concat({
            id: nextId,
            text: inputText
        });
        setNextId(nextId + 1); // nextId값에 1을 더해줌
        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;

input 박스에 onChange 이벤트가 일어날 때마다 setInputText로 inputText 값을 set해줍니다.

 

button에 onClick 이벤트가 발생하면, 기존 배열(names)를 concat을 사용하여 새로운 항목을 추가한 배열을 생성해주고, setNames를 통해 names 배열을 업데이트 해줍니다.

또한 다음 id값을 처리해주기 위하여 nextId값에 + 1 처리를 해주고, inputText는 초기화해줍니다.

 

※ push 함수가 아닌 concat을 사용한 이유?

push 함수는 기존 배열 자체를 변경하지만, concat은 새로운 배열을 생성해줍니다.

리액트에서는 상태를 업데이트할 때 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 하는데, 이를 불변성 유지라고 합니다.

불변성 유지를 해야 성능을 최적화할 수 있음!

 

데이터 제거 기능 구현하기

 

더블 클릭 시 항목을 제거할 것이며, 이 또한 불변성 유지를 해 줘야 합니다.

 

IterationSample.js

    ...
    ...
    
    // 더블클릭 시 삭제
    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>
        </>
    )

해당 요소가 잘 제거 되는 것을 확인할 수 있습니다.

 

☆ 상태 안에서 배열을 변형할 때는 배열에 직접 접근하는 것이 아닌, concat, filter 등을 이용하여 새로운 배열을 생성한 후 이를 새로운 상태로 설정해줘야 함!!!

 

https://chanychu.tistory.com/252

 

Javascript) 배열(3) (유사배열-Array.from, [...Array] / 배열반복처리-for..in문, for..of문, forEach() / map / filter

안녕하세요, 코린이의 코딩 학습기 채니 입니다. 개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다. 유사배열 - Array.prototype을 상속하지 않은 배열 객체 - index, length는 있지만 Ar

chanychu.tistory.com