const IterationSample = () => {
return (
<ul>
<li>눈사람</li>
<li>얼음</li>
<li>눈</li>
<li>바람</li>
</ul>
);
};
export default IterationSample;
코드가 다음 형태로 반복 된다.
지금은 li만 있어서 크게 문제가 되지 않지만, 코드가 복잡해진다면 용량도 늘어나면서 효율적이지 못하다. 그리고 보여줘야할 데이터가 유동적이라면 코드 관리가 힘들 것이다.
1. 자바스크립트 배열의 map()함수
2. 문법
자바스크립트 배열 객체의 내장 함수인 map 함수를 사용하여 반복되는 컴포넌트를 렌더링 할 수 있다. map 함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.
arr.map(callback, [thisArg])
이 함수의 파라미터는 다음과 같다.
- callback: 새로운 배열의 요소를 생성하는 파라미터는 다음 세가지이다.
- currentValue: 현재 처리하고 있는 요소
- index: 현재 처리하고 있는 요소의 index 값
- array: 현재 처리하고 있는 원본 배열
- thisArg(선택 항목): callback 함수 내부에서 사용할 this 레퍼런드
3. 예제
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);
위와 같이 결과가 나온다.
이처럼 map 함수는 기존 배열로 새로운 배열을 만드는 역할을 한다. 이 코드를 ES6 문법으로 작성한다.
const numbers = [1, 2, 3, 4, 5]
const result = numbers.map(num => num * num);
console.log(result);
var 키워드 대신 const를 사용했고, function(...){...} 대신 화살표 함수를 사용했다.
4. 데이터 배열을 컴포넌트 배열로 변환하기
4-1. 컴포넌트 수정하기
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(name => <li>{name}</li>);
return <ul>{nameList}</ul>
;}
export default IterationSample;
문자열로 구성된 배열을 선언한다. 그 배열 값을 사용하여 <li>...</li> JSX 코드로 된 배열을 새로 생성 후 nameList에 담는다.
map 함수에서 JSX를 작성할 때는 앞서 다룬 예제처럼 DOM 요소를 작성해도 되고, 컴포넌트를 사용해도 된다.
4-2. App 컴포넌트에서 예제 컴포넌트 렌더링
import { Component } from "react";
import IterationSample from "./IterationSample";
class App extends Component {
render() {
return (
<IterationSample />
);
}
}
export default App;
이렇게 실행을 하면
"key" prop이 없다는 경고가 뜬다.
5. key
리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다. 예를 들면 유동적인 데이터를 다룰 때는 원소를 새로 생성할 수도, 제거할 수도, 수정할수도 있다. key가 없을 때는 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지한다. 하지만 key가 있다면 이 값을 사용하여 어떤 변화가 일어났는지 빠르게 파악할 수 있다.
5-1. key 설정
key 값을 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정하면 된다. key 값은 언제나 유일해야 한다. 따라서 데이터가 가진 고유값을 key값으로 설정해야 한다.
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map((name, index) => <li key={index}>{name}</li>);
return <ul>{nameList}</ul>
;
}
export default IterationSample;
고유 값이 없을 때만 index 값을 key로 사용해야 한다. index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못한다.
6. 응용
고정된 배열을 렌더링 하는 것이 아닌, 동적인 배열을 렌더링하는 것을 구현 할 것이다. 그리고 index 값을 key로 사용하면 리렌더링이 비효율적인데, 이러한 상황에서 고유값을 어떻게 만들 수 있는지 확인 해볼 것이다.
유동적인 데이터 렌더링
초기 상태 설정하기 -> 데이터 추가 기능 구현하기 -> 데이터 제거 기능 구현하기
6-1. 초기 상태 설정하기
IterationSample 컴포넌트에서 useState를 사용하여 상태를 설정하겠다. 세가지 상태를 설정할 건데,
1. 데이터 배열
2. 텍스트를 입력할 수 있는 input 상태
3, 데이터 배열에서 새로운 항목을 추가할 수 있는 사용할 고유 id를 위한 상태이다.
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);
const nameList = names.map(name => <li key={name.id}>{name.text}</li>);
return <ul>{nameList}</ul>
};
export default IterationSample;
이번에 map함수를 사용할 때는 key 값을 index 대신 name.id값으로 지정했다.
6-2. 데이터 추가 기능 구현하기
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>
<ul>{nameList}</ul>
</>
};
export default IterationSample;
새로운 이름을 등록할 수 있는 기능 구현했다. ul 태그의 상단에 input과 button을 렌더링하고, input 상태관리를 한다.
이 다음 버튼을 클릭했을 때 호출할 onClick 함수를 선언하여 버튼의 onClick 이벤트를 설정한다.
6-3. - 추가 버튼 누르면 추가 -
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;
-
배열에 새 항목을 추가할 때 배열의 push 함수를 사용하지 않고 concat을 사용했다. push 함수는 기존 배열 자체를 변경해주는 반면, concat은 새로운 배열을 만들어 준다는 차이가 있다. 리액트에서 상태를 업데이트 할 때는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야한다. 이를 불변성 유지라고 한다. 불변성 유지를 해 주어야 나중에 리액트 컴포넌트의 성능을 최적화 할 수 있다.
onClick 함수에서 새로운 항목을 추가할 때 객체의 id 값은 nextId를 사용하도록 하고, 클릭될 때마다 값이 1씩 올라가도록 구현했다. 추가로 button이 클릭 될 때 기존의 input 내용을 비우는 것도 구현했다.
6-4. 데이터 제거 기능 구현하기
각 항목을 더블 클릭 했을 때 해당 항목이 화면에서 사라지는 기능 구현할 것이다.
이번에도 불변성을 유지하면서 업데이트를 해줘야 한다. 불변성을 유지하면서 배열의 특정 항목을 지울 때는 배열의 내장 함수 filter를 사용해야 한다.
filter 함수를 사용하면 배열에서 특정 조건을 만족하는 원소들만 쉽게 분류할 수 있다.
예시
6-5. - 3아래 없애기 -
const numbers = [1, 2, 3, 4, 5, 6];
const biggerThanThree = numbers.filter(numbers => numbers > 3);
// 결과 : [4, 5, 6]
filter 함수의 인자에 분류하고 싶은 조건을 반환하는 함수를 넣어주면 쉽게 분류 할 수 있다.
이 filter 함수를 응용하여 특정 배열에서 특정 원소만 제외시킬 수도 있다. 예를 들면 위 코드에서 본 numbers 배열에서 3만 없애고 싶다면 아래와 같다.
6-6. - 3만 없애기 -
const numbers = [1, 2, 3, 4, 5, 6];
const withOutThree = numbers.filter(numbers => numbers !== 3);
// 결과 : [1, 2, 4, 5, 6]
filter 함수를 사용하여 IterationSample 컴포넌트의 항목 제거 기능을 구현해야한다.
HTMLA 요소를 더블클릭 할 때 사용하는 이벤트 이름은 onDoubleClick이다. onRemove라는 함수를 만들어서 각 li 요소에 이벤트 등록을 하자.
6-7. - 더블 클릭하면 요소 삭제 -
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;
7. 정리
반복되는 데이터를 렌덩링하는 방법을 배웠다. 이를 응용하여 유동적인 배열을 다루었다. 컴포넌트 배열을 렌더링 할 때는
key 값 설정에 항상 조심해야 한다.
또 key 값은 언제나 유일해야 한다.
key 값이 중복된다면 렌더링 과정에서 오류가 발생한다.
상태 안에서 배열을 변형할 때는 배열에 직접 접근하여 수정하는 것이 아니라 concat, filter 등의 배열 내장 함수를 사용하여
새로운 배열을 만든 후 이를 새로운 상태로 설정해주어야 한다!
!! 불변성을 유지해야 한다 !!
'React > 다시 공부하는 리액트' 카테고리의 다른 글
다시 공부하는 리액트 3장 component, state (0) | 2023.07.02 |
---|---|
7장. 컴포넌트의 라이프사이클 메서드 (0) | 2023.06.22 |
4장. 이벤트 핸들링 (0) | 2023.06.20 |
20230617 3장. 컴포넌트 (0) | 2023.06.17 |
20230617 2장. JSX (0) | 2023.06.17 |