Front-End

컴포넌트 반복 본문

리액트

컴포넌트 반복

jeongsso 2023. 2. 27. 18:57

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

src 디렉터리에 IterationSample.js라는 코드를 한번 봅시다.

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

export default IterationSample;

<li> ... </li> 형태가 계속 반복 되는 것을 볼 수 있습니다.

여기서는 4개지만 더 여러 개인 경우 복잡할 수 있습니다.

 

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

 

 

 

 

 

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

자바스크립트 배열 객체의 내장 함수인 map 함수를 사용하여 반복되는 컴포넌트를 렌더링 할 수 있습니다.

map 함수파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후

그 결과로 새로운 배열을 생성합니다.

 

 

=   (1)  문법   =

arr.map(callback, [thisArg])

 

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

 

☞  callback

: 새로운 배열의 요소를 생성하는 함수로 파라미터는 다음 세 가지입니다.

-  currentValue  :  현재 처리하고 있는 요소

-  index  :  현재 처리하고 있는 요소의 index 값

- array  :  현재 처리하고 있는 원본 배열

 

☞  thisArg(선택 함수)

:  callback 함수 내부에서 사용할 this 레퍼런스

 

 

 

=   (2)  예제   =

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); // [1, 4, 9, 16, 25]

이처럼 map 함수는 기존 배열로 새로운 배열을 만듭니다.

 

화살표 함수로 작성한 코드랑 비교해 봅시다.

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

const result = numbers.map(num =>  num * num);

console.log(result); // [1, 4, 9, 16, 25]

 

 

 

 


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

=   (1)  컴포넌트 수정하기   =

조금 전 만들었던걸 수정해 봅시다.

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

export default IterationSample;

 

 

 

=   (2)  App 컴포넌트에서 예제 컴포넌트 렌더링   =

 

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

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

exporrt default App;

화면에서는 우리가 원하던 대로 렌더링이 됩니다.

하지만 관리자 권한에서 콘솔을 켜면..!

"key" prop이 없다는 경고메시지가 뜰 겁니다.

 

 

 

 

 


=    3.  key    =

key 값을 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하는 듯이 설정하면 된다.

key 값은 언제나 유일해야 한다.

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

 

// IterationSample.js
const IterationSample = () => {
  const names = ['눈사람', '얼음', '눈', '바람'];
  
  // 이렇게 index로 key 값으로하면된다.
  const nameList = names.map((name, index) => <li key = {index}> {name} </li>);
  return <ul>{nameList}</ul>;
};

export default IterationSample;

 

고유한 값이 있을 때만 

index 값을 key 로 사용해야 합니다.

 

 

 


=    4.  응용    =

지금까지 배운 것을 응용하여 고정된 배열을 렌더링하는 것이 아닌,

동적인 배열을 렌더링하는 것을 구현해 봅시다.

 

 

 

=   (1)  초기 상태 설정하기   =

IterationSample 컴포넌트에서 useState를 사용하여 상태를 설정하겠습니다.

 

세 가지 상태를 사용할 텐데

하나는 데이터 배열,

다른 하나는 텍스트를 입력할 수 있는 input 상태,

마지막은 데이터 배열에서 새로운 항목을 추가할 때 사용할 고유 id를 위한 상태다.

 

배열을 설정할 때,

위에서는 단순히 문자열로 이루어진 배열을 만들었지만,

이번에는 객체 형태로 이루어진 배열을 만들고, 각 객체에는 문자열과 고유한 id 값이 있다.

 

 

// IterationSample.js
import {useState} from 'react';

const IterationSample = () => {
  const [names, setNames] = useState ([
    {id : 1, text : '눈사람'},
    {id : 1, text : '얼음'},
    {id : 1, text : '눈'},
    {id : 1, 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 값으로 지정했습니다.

 

 

 

=   (2)  데이터 추가 기능 구현하기   =

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

우선 ul 태그 상단에 input과 button을 렌더링하고, input 상태관리를 해보자.

// IterationSample.js
import {useState} from 'react';

const IterationSample = () => {
  const [names, setNames] = useState ([
    {id : 1, text : '눈사람'},
    {id : 1, text : '얼음'},
    {id : 1, text : '눈'},
    {id : 1, 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 함수에서는 배열의 내장 함수 concat을 사용하여

새로운 항목을 추가한 배열을 만들고,

setNames를 통해 상태를 업데이트 해준다.

// IterationSample.js
import {useState} from 'react';

const IterationSample = () => {
  const [names, setNames] = useState ([
    {id : 1, text : '눈사람'},
    {id : 1, text : '얼음'},
    {id : 1, text : '눈'},
    {id : 1, text : '바람'}
  ]);
  
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); // 새로운 항목을 추가할 때 사용할 id
  
  const onChange = e => setInputText(e.target.value);
  const onClick =() => {
    const nextNames = name.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;

 

배열에 새 항목을 추가할 때 배열의 push 함수를 사용하지 않고, concat을 사용했습니다.

push는 기존 배열 자체를 변경하고,

concat은 새로운 배열을 만들어주는 차이가 있기 때문이다.

 

 

리액트에서 상태를 업데이트할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야한다.

이를 불변성의 유지라고 한다.

 

불변성의 유지를 해줘야 나중에 리액트 컴포넌트 성능을 최적화 할 수 있다.

 

 

 

 

 

=   (3)  데이터 제거 기능 구현하기   =

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

이번에도 불변성을 유지하면서 업데이트를 해야한다.

 

불변성을 유지하면서 배열의 특정 항목을 지울 때는 배열의 내장 함수 filter를 사용한다.

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

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

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

 

 

이제 filter 함수를 사용하여 IterationSample 컴포넌트의 항목 제거 기능을 구현해 봅시다.

// IterationSample.js
import {useState} from 'react';

const IterationSample = () => {
  const [names, setNames] = useState ([
    {id : 1, text : '눈사람'},
    {id : 1, text : '얼음'},
    {id : 1, text : '눈'},
    {id : 1, text : '바람'}
  ]);
  
  const [inputText, setInputText] = useState('');
  const [nextId, setNextId] = useState(5); // 새로운 항목을 추가할 때 사용할 id
  
  const onChange = e => setInputText(e.target.value);
  const onClick =() => {
    const nextNames = name.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 IterationSample;

 

 

 

 

컴포넌트 배열을 렌더링할 때는 key 값 설정에 항상 주의해야 합니다.

또 key 값은 언제나 유일해야 합니다.

key 값이 중복된다면 렌더링 과정에서 오류가 발생합니다.


상태 안에서 배열을 변형할 때는 배열에 직접 접근하여 수정하는 것이 아니라 

concat, filter 등 의 배열 내장 함수를 사용하여 

새로운 배열을 만든 후 이를 새로운 상태로 설정해 주어야 한다는 주의해야한다.. 

'리액트' 카테고리의 다른 글

Hooks  (1) 2023.03.02
컴포넌트의 라이프사이클 메서드  (0) 2023.02.28
ref : DOM에 이름 달기  (1) 2023.02.23
이벤트 핸들링  (0) 2023.02.20
컴포넌트  (0) 2023.02.18
Comments