다라다라V
article thumbnail
728x90
반응형

웹 어플리케이션을 만들다 보면 반복되는 코드를 작성해야 할 때가 있습니다.

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

export default IterationSample;
다음과 같이 <li>..</li> 코드가 반복되는 양이 많아질 수록 코드양은 늘어나고 파일용량도 쓸데없이 늘어날 것입니다. 또한 데이터가 유동적이라면 이런 코드로는 관리할 수 없습니다. 리액트 프로젝트에서는 이러한 
반복적인 내용을 효율적으로 관리하는 방법을 제공합니다.

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

📚 문법

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

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

📚 예제

map 함수를 이용하여 각 요소를 제곱하는 새로운 배열을 만들어 봅시다.

 

크롬 개발자 도구(F12)를 열어 다음의 코드를 작성해보세요.

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

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

console.log(processed); // result : [1, 4, 9, 16, 25]
콘솔에서 Shift 를 누른 채 Enter을 누르면 명령어를 실행시키지 않고 새 줄을 들여쓸 수 있습니다.

코드를 실행시키면 제곱된 새로운 배열이 출력된 것을 확인해볼 수 있습니다.

 

다음의 코드를 ES6 문법으로 const 키워드화살표 함수를 이용하여 작성해보면 다음과 같습니다,

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

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

📚 컴포넌트 수정하기

다음과 같이 IterationSample 컴포넌트를 수정해봅시다.

// IterationSample.js

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

export default IterationSample;

이 코드는 문자열로 구성된 배열을 선언합니다. 이 배열의 값을 이용하여 <li> ... </li> JSX 코드로 된 배열을 생성하고 nameList에 담습니다. 


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

App 컴포넌트에서 IterationSample  컴포넌트를 불러와 렌더링 해봅시다.

// App.js

import { Component } from "react";
import IterationSample from "./IterationSample";

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

export default App;

웹 브라우저를 체크해보면 원하는대로 렌더링 된 것을 확인할 수 있습니다. 

 

그러나 크롬 개발자 도구(F12)에서 콘솔을 열어보면 "key" prop이 없다는 Warning 메시지를 확인할 수 있습니다.


📌 key

📚 key

리액트에서 key 는 컴포넌트 배열을 렌더링했을 때, 어떤 원소에 변동이 있는지를 확인할 때 사용합니다. 유동적인 데이터를 다루면 원소를 삽입하거나 삭제하고, 수정할 수 있습니다.

  1. key가 없을 때는 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하며 변화를 감지
  2. key가 있을 때key 값을 사용하여 어떤 변화가 일어나는지 빠르게 알아볼 수 있습니다.

📚 key 설정

key 값을 설정할 때는 map 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯 설정하면 됩니다. key 값은 언제나 유일해야하므로 데이터가 가진 고윳값을 key 값으로 설정해야합니다.

 

만약 게시판의 게시물을 렌더링한다면 게시물 번호가 key 값이 될 것입니다.

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

 

하지만 앞선 <li>...</li> 같은 경우는 고유 번호가 없습니다. 이 경우에는 map 함수에 전달되는 콜백 함수의 인수인 index 값을 사용하면됩니다.

// IterationSample.js

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

export default IterationSample;

key 값을 설정했으므로 콘솔에서 더이상 경고 메시지를 표시하지 않습니다.

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

📌 응용

지금까지 배운 내용을 바탕으로 동적인 배열을 리렌더링하는 것을 구현해봅시다. index 값을 key 로 사용하지 않고 고윳값을 만드는 방법을 배워봅시다.

 

📚 초기 상태 설정하기

IterationSample 컴포넌트에서 useState 를 사용하여 상태를 설정해봅시다. useState에서는 세 가지 상태를 사용합니다.

  • 데이터 배열의 상태
  • 텍스트를 입력하는 input의 상태
  • 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태 

이 세 가지 상태를 이용합니다. 객체 형태로 이루어진 배열을 만들어 봅시다.

// IterationSample.js

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

export default IterationSample;
  • map 함수를 사용할 때 key 값을 index 대신 name.id 값으로 지정
  • 브라우저에서 확인하면 이전과 같은 결과를 얻을 수 있음

📚 데이터 추가 기능 구현하기

새로운 이름을 등록하는 기능을 구현해봅시다.

 

📃 input 상태 관리

ul 태그의 상단에 inputbuttom 을 렌더링하고 input의 상태를 관리

// IterationSample.js

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('');
    const [nextId, setNextId] = useState(5); //새로운 항목을 추가할 때 사용할 id

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

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

export default IterationSample;

 

📃 onClick 이벤트 설정

버튼을 클릭했을 때 호출할 onClick 함수를 선언하여 onClick 이벤트로 설정

// IterationSample.js

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('');
    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 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;
  • onClick 함수에서는 배열의 내장 함수 concat을 사용하여 새로운 항목을 추가한 배열을 만듦
  • setNames를 통해 상태를 업데이트
  • onClick 함수에서 새로운 항목을 추가할 때 객체의 id 값은 nextId를 사용하도록 함
  • 클릭될 때마다 값이 1씩 올라가도록 구현
  • button이 클릭될 때 기존의 input 내용을 비우는 것도 구현

 

📃 불변성 유지

  • push 함수는 기존 배열 자체를 변경, concat 함수 새로운 배열을 만들어줌
  • 리액트는 상태를 업데이트할 때 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야함 (불변성 유지)
  • 불변성 유지를 해야 나중에 리액트 컴포넌트 성능을 최적화할 수 있음

📚 데이터 제거 기능 구현하기

각 항목을 더블클릭했을 때 해당 항목이 화면에서 사리지는 기능을 구현해봅시다. 마찬가지로 불변성을 유지하면서 업데이트 해야합니다. 불변성을 유지하면서 배열의 특정항목을 지울 때는 배열의 내장 함수 filter를 사용해야합니다.

 

📃 filter 함수

filter 함수를 사용하면 배열의 특정 조건을 만족하는 원소만 분류할 수 있습니다. 

 

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

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

 

filter함수를 응용하여 특정 배열에서 특정 원소만 제외할 수도 있습니다.

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

 

📃 IterationSample 컴포넌트

filter 함수를 이용하여 IterationSample 컴포넌트의 제거 기능을 구현해봅시다. HTML 요소를 더블클릭할 때 사용하는 이벤트 이름은 OnDoubleClick 입니다. onRemove라는 함수를 만들어 이벤트 등록을 합시다.

// IterationSample.js

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('');
    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 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;

📌 정리

반복되는 데이터를 렌더링하는 방법을 배웠습니다. 또한 이를 응용하여 유동적인 배열을 다루어보았습니다. 컴포넌트 배열을 렌더링할 때는 key 값 설정에 항상 주의해야합니다. 상태 안에서 배열을 변형할 때는 배열을 변형할 때는 배열에 직접 접근하여 수정하지 말고 concat, filter배열 내장 함수를 이용하여 새로운 배열을 만들어야합니다. 

반응형
profile

다라다라V

@DaraDaraV

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!