쌩로그

리액트를 다루는 기술 - 06. 컴포넌트 반복 본문

Front/리액트를 다루는 기술

리액트를 다루는 기술 - 06. 컴포넌트 반복

.쌩수. 2024. 4. 10. 21:35
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. 자바스크립트 배열의 map() 함수
      2-2. 데이터 배열을 컴포넌트 배열로 변환하기
      2-3. key
      2-4. 응용
  3. 요약

1. 포스팅 개요

해당 포스팅은 리액트를 다루는 기술06장 컴포넌트 반복 을 학습하며 정리한 포스팅이다.

2. 본론

웹 애플리케이션을 만들다 보면 다음과 같이 반복되는 코드를 작성할 때가 있다.

import React from "react";  

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

export default IteratorSampleOfSample;

코드에서 다음 형태가 계속 반복되는 것을 볼 수 있다.

<li>...</li>

만약
코드가 좀 더 복잡하다면, 코드양이 늘어날 것이며, 파일 용량도 쓸데없이 증가살 것이다.
이는 낭비다.
또한 보여주어야 할 데이터가 유동적이라면 이런 코드로는 절대 관리하지 못한다.

이 장에서는 리액트 프로젝트에서 반복적인 내용을 효율적으로 보여 주고 관리하는 방법을 알아본다.

2-1. 자바스크립트 배열의 map() 함수

자바스크립트 배열 객체의 내장 함수인 map 함수를 사용하여 반복되는 컴포넌트를 렌더링할 수 있다.
map 함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.

문법

arr.map(callback, [thisArg])

이 함수의 파라미터는 다음과 같다.

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

예제

map 함수를 사용하여 배열 [1,2,3,4,5]의 각 요소를 제곱해서 새로운 배열을 생성한다.

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

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

console.log(processed);

위 함수를 크롬 개발자 도구로 확인하면 다음과 같다.

참고

개발자 도구 콘솔에서 shift를 누른 채 Enter를 누르면 명령어가 바로 실행되지 않고, 새 줄을 들여쓸 수 있다. 

ES6 문법을 사용하면 다음과 같다.

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

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

컴포넌트를 다음과 같이 작성해보자.

import React from "react";  


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

export default IteratorSample;

문자열로 구성된 배열을 선언한다.
그 배열 값을 사용하여 <li>...</li> JSX 코드로 된 배열을 새로 생성한 후 nameList에 담는다.

map 함수에서 JSX를 작성할 때 DOM 요소를 작성해도 되고, 컴포넌트를 사용해도 된다.

결과는 다음과 같다.

그러나 아직 완벽하지 않다.

"key" prop 이 없다는 경고 메세지를 표시한다.

2-3. key

리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다.

key가 없을 때는 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지한다.
key가 있다면 이 값을 사용하여 어떤 변화가 일어났는지 더욱 빠르게 알아낼 수 있다.

key 설정

key 값을 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정하면 된다.
key 값은 언제가 유일해야 한다.

데이터가 가진 고윳값을 key 값으로 설정해야 한다.

예를 들어 게시판의 게시물을 렌더링한다면 게시물 번호를 key 값으로 설정해야 한다.

const articleList = articles.map(article => (
    <Article
        title={article.title}
        writer={article.writer}
        key={article.id}
    />
));

앞서 만들었던 예제 펌포넌트에는 이런 고유 번호가 없다.
이때는 map 함수에 전달되는 콜백 함수의 인수인 index 값을 사용하면 된다.

아래와 같이 코드를 수정해보자.

import React from "react";  

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

export default IteratorSample;

이제 개발자 도구에서 더 이상 경고 메세지를 표시하지 않는다.

참고로 고유한 값이 없을 때만 index 값을 key로 사용해야 한다.
index를 key로 사용하면 배열이 변경될 떄 효율적으로 리렌더링하지 못한다.

2-4. 응용

동적인 배열을 렌더링하는 것을 구현해보자.
index 값을 key로 사용하면 리렌더링이 비효율적이라고 배웠는데, 이러한 상황에 어떻게 고윳값을 만들 수 있는지도 알아보자.

초기 상태 설정하기

IterationSample 컴포넌트에서 useState를 사용하려 상태를 설정한다.
세 가지 상태를 사용할 텐데 하나는 데이터 배열이고, 다른 하나는 텍스트를 입력할 수 있는 input의 상태이다. 다른 하나는 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태이다.

이번에는 배열을 설정할 때 객체 형태로 이루어진 배열을 만들어본다.

해당 객체에는 문자열과 고유 id 값이 있다.

import React, {useState} from "react";  

const IteratorObjectsSample = () => {  
    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 nameList = names.map(name => <li key={name.id}>{name.text}</li>);  

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

export default IteratorObjectsSample;

데이터 추가 기능 구현

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

import React, {useState} from "react";  

const IteratorObjectsSample = () => {  
    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);  // 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 IteratorObjectsSample;

확인해보면 추가가 되는 것을 확인할 수 있다.

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

리엑트에서 상태를 업데이트할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 한다.
이를 불변성 유지라고 하는데, 불변성 유지를 해주어야 나중에 리액트 컴포넌트의 성능을 최적화할 수 있다.

데이터 제거 기능 구현하기

이번에는 각 항목을 더블클릭했을 때 해당 항목이 화면에서 사라지는 기능을 구현해보자.

마찬가지로 불변성을 유지하면서 업데이트해 주어야 한다.
불변성을 유지하면서 배열의 특정 항목을 지울 때는 배열의 내장 함수 filter를 사용한다.

filter 함수를 사용하면 배열에서 특정 조건을 만족하는 원소들만 쉽게 분류할 수 있다.

예시를 한번 보자.

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

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

이제 데이터 제거 기능을 구현할 것인데,
HTML 요소를 더블클릭할 때 사용하는 이벤트 이름은 onDoubleClick이다.
onRemove라는 함수를 만들어서 각 li 요소에 이벤트 등록을 해준다.

import React, {useState} from "react";  

const IteratorObjectsSample = () => {  
    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);  // nextId 값에 1을 더해준다.  
        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 IteratorObjectsSample;

아래는 초깃값이다.

눈사람과 바람을 더블클릭하여 제거했다.

추가도 된다.

3. 요약

반복되는 데이터를 렌더링하는 방법을 배우고,
이를 응용하여 유동적인 배열을 다루어보았다.

key 값 설정에 항상 주의해야 한다. 또한
key 값은 언제나 유일해야 한다.
key 값이 중복된다면 렌더링 과정에서 오류가 발생한다.

상태 안에서 배열을 변형할 때는 배열에 직접 접근하여 수정하는 것이 아니라, concat, filter 등의 배열 내장 함수를 사용하여 새로운 배열을 만든 후 이를 새로운 상태로 설정해 주어야 한다.

728x90
Comments