본문 바로가기

Front End/React

[React Exam 29] map()을 이용한 반복 렌더링

컴포넌트를 렌더링 하면서 반복되는 코드들 때문에 코드 줄이 길어지고 이러한 문제를 반복문으로 해결할 수 없을까 고민하면서 방법을 찾아보게 되었습니다.

 

다음과 같은 데이터가 있다고 할때

const NAMES = "한놈,두시기,석삼,너구리,오징어,육계장,칠면조,팔다리,구두쇠,쨍그랑".split(",");

 

반복문이 없다면 다음과 같은 코드가 만들어 집니다.

const LoopRendering1 = ()=>{
    return (
        <ul style={{clear:"both"}}>
            {/* 반복문이 없으면 난감하다. */}
            <li>{NAMES[0]}</li>
            <li>{NAMES[1]}</li>
            <li>{NAMES[2]}</li>
            <li>{NAMES[3]}</li>
            <li>{NAMES[4]}</li>
            <li>{NAMES[5]}</li>
            <li>{NAMES[6]}</li>
            <li>{NAMES[7]}</li>
            <li>{NAMES[8]}</li>
            <li>{NAMES[9]}</li>
        </ul>
    );
}

 

실행은 잘 되지만 아주 좋지 않은 코드가 됩니다. 데이터가 더 많아 진다면 어떻게 할까요? 무척이나 난감한 코드가 되고 맙니다. 이럴때 반복문을 사용하면 됩니다.

 

그럼 다음과 같이 for반복문을 사용하면 될까요? 

return문 안에서는 아래처럼 for문을 사용하면 안 됩니다. return문 안에서는 JSX를 '반환' 해주어야 합니다. 

잘못된 반복문의 사용입니다.

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
            {/* return문 안에서는 아래처럼 for문을 사용하면 안 됩니다. */}
            {/*
                for(let i=0;i<NAMES.length;i++){
                    return <li>{NAMES[i]}</li>
                }
            */}
        </ul>
    );
}

 

그럼 다시 다음과 같이 forEach 메서드를 사용하면 될까요? 

return문 안에서는 JSX를 '반환' 해주어야 한다고 했습니다. forEach는 배열의 요소를 순회만 할 뿐 반환해 주는 메서드가 아니기 때문에 사용할 수 없습니다. 
에러는 아니지만 아무것도 출력되지 않습니다.

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
           {
                NAMES.forEach((n)=>{
                    <li>{n}</li>
                })
            }
        </ul>
    );
}

 

그렇다면 어떻게 사용해야 할까요? 바로 map() 메서드를 사용하는것 입니다.

map 메서드는 요소를 순회하며 값을 반환해 주기 때문에 사용이 가능합니다. 
map 순회 함수는 반드시 JSX를 반환해야 합니다. 

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
           {
                NAMES.map((n)=>{
                    return <li>{n}</li>
                })
            }
        </ul>
    );
}

 

반복문을 사용할 때에는 key값을 필수로 지정해주어야 합니다.

 

key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕습니다.

key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 합니다.


key를 사용하지 않으면 다음과 같은 경고가 나옵니다. 
Warning: Each child in a list should have a unique "key" prop.

( 경고:  목록의 각 하위 항목에는 고유한 "key"가 있어야 합니다.)

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
           {
                NAMES.map((n)=>{
                    return <li key={1}>{n}</li>
                })
            }
        </ul>
    );
}

            
key값은 유일한 값으로 주어야 합니다. 동일한 키값을 주면 다음과 같은 경고가 나옵니다.
Warning: Encountered two children with the same key, `1`.

(경고: 동일한 키 `1`을 가진 두 하위 항목이 발견되었습니다.)

 

key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것입니다. 대부분의 경우 데이터의 ID를 key로 사용합니다.

렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있습니다.

 

다음과 같이 해야만 경고가 발생하지 않습니다.

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
           {
                NAMES.map((n, index)=>{
                    return <li key={index}>{n}</li>
                })
            }
        </ul>
    );
}

 

하지만 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않습니다. 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있습니다.

리스트 항목에 명시적으로 key를 지정하지 않으면 React는 기본적으로 인덱스를 key로 사용합니다.

자세한 내용은 다음을 참조하시기 바랍니다.

https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318 

 

Index as a key is an anti-pattern

So many times I have seen developers use the index of an item as its key when they render a list.

robinpokorny.medium.com

 

위의 내용은 다음에 다시 한번 설명하도록 하겠습니다.

 

다음과 같이 하는것이 올바른 방법입니다.

const LoopRendering2 = ()=>{
    return (
        <ul style={{clear:"both"}}>
           {
                NAMES.map((n)=>{
                    return <li key={n}>{n}</li>
                })
            }
        </ul>
    );
}

 

리턴 밖에서  연산을 수행하는 함수를 만들어 놓고 함수를 호출해도 됩니다.

 const LoopRendering2 = ()=>{
    // 함수로 새로운 배열을 만들어 리턴하도록 합니다.
    const rendering1 = () => {
        const result = [];
        for (let i = 0; i < NAMES.length; i++) {
            result.push(<li key={i}>{NAMES[i]}</li>);
        }
        return result;
    };  
    return (
        <ul style={{clear:"both"}}>
           {
              rendering1()
            }
        </ul>
    );
}

 

하지만 위의 내용은 올바른 방법이 아닙니다.

map()메서드가 새로운 배열을 만들어 리턴하는 메서드이기 때문에 다음과 같이 하면 됩니다.

const LoopRendering2 = ()=>{
    // map()메서드는 컬렉션을 반복하며 새로운 배열을 만들어 리턴해 줍니다.
    // 만들어진 새로운 배열을 변수에 담아 줍니다.
    const rendering2 = NAMES.map((n)=> <li key={n}>{n}</li>)
 
    return (
        <ul style={{clear:"both"}}>
           {
              rendering2
            }
        </ul>
    );
}

 

 

LoopRendering.css 파일을 파일을 만듭니다.

1
2
3
4
5
6
7
8
9
10
li {
    float: left;
    margin-right: 20px;
    width: 80px;
    list-style: none;
}
li::before{
    content: '👍 ';    
}
 
cs
 

LoopRendering.js 파일을 파일을 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import './LoopRendering.css'
const NAMES = "한놈,두시기,석삼,너구리,오징어,육계장,칠면조,팔다리,구두쇠,쨍그랑".split(",");
export const LoopRendering1 = ()=>{
    return (
        <ul style={{clear:"both"}}>
            {/* 반복문이 없으면 난감하다. */}
            <li>{NAMES[0]}</li>
            <li>{NAMES[1]}</li>
            <li>{NAMES[2]}</li>
            <li>{NAMES[3]}</li>
            <li>{NAMES[4]}</li>
            <li>{NAMES[5]}</li>
            <li>{NAMES[6]}</li>
            <li>{NAMES[7]}</li>
            <li>{NAMES[8]}</li>
            <li>{NAMES[9]}</li>
        </ul>
    );
}
 
export const LoopRendering2 = ()=>{
    // 함수로 새로운 배열을 만들어 리턴하도록 합니다.
    const rendering1 = () => {
        const result = [];
        for (let i = 0; i < NAMES.length; i++) {
            result.push(<li key={i}>{NAMES[i]}</li>);
        }
        return result;
    };    
    
    // map()메서드는 컬렉션을 반복하며 새로운 배열을 만들어 리턴해 줍니다.
    // 만들어진 새로운 배열을 변수에 담아 줍니다. 
    const rendering2 = NAMES.map((n)=> <li key={n}>{n}</li>)
 
    return (
        <ul style={{clear:"both"}}>
            {/* return문 안에서는 아래처럼 for문을 사용하면 안 됩니다. */}
            {/*
                for(let i=0;i<NAMES.length;i++){
                    return <li>{NAMES[i]}</li>
                }
            */}
            {
                NAMES.forEach((n)=>{
                    <li>{n}</li>
                })
            }
            {
                NAMES.map((n)=>{
                    return <li>{n}</li>
                }) 
            }
            <br/>
            {
                NAMES.map((n)=>{
                    return <li key={1}>{n}</li>
                }) 
            }
            <br/>
            {
                NAMES.map((n)=> <li key={n}>{n}</li>)
            }
            <br/>
            {/* 함수를 만들어 호출해도 됩니다. */}
            {
                rendering1()
            }
            <br/>
            {/* map()메서드로 새로운 배열을 만들어 넣어도 됩니다. */}
            {rendering2}
        </ul>
    );
}
 
 
 
 
cs
 

 

App.js 파일을 파일을 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
import * as Loop from './LoopRendering'
 
const App = ()=>{
    return (
        <>
          <Loop.LoopRendering1/>
          <Loop.LoopRendering2/>
        </>
    )
}
export default App;
cs

 

 

index.js 파일을 파일을 만듭니다.

1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
 
const root = ReactDOM.createRoot(document.querySelector("#app"));
root.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
);    
cs
 
 
 
결과 화면입니다.