react - v17.0.2
typescript - v4.3.2
html태그 중 ul, li, ol 태그는 목록 데이터를 그려주는 태그입니다. 데이터가 감당할 수 있는 수준에서는 일일이 위의 태그를 직접 활용하여 반영할 수 있겠지만, 데이터가 많아지면 위와 같은 방식으로는 더이상 렌더링하기 쉽지 않다.
이때 사용할 수 있는 함수가 map 함수인데, DOM요소, 컴포넌트를 반복하고 싶을 때 map 함수를 이용하여 컴포넌트를 쉽게 렌더링 할 수 있다.
import React from 'react';
// map활용 컴포넌트 반복하기
function IterationSample() {
const names: string[] = ['짜장면', '짬뽕', '탕수육'];
// map함수로 배열을 새로 생성한 후 return으로 컴포넌트를 반환할 수 있습니다.
const nameList: JSX.Element[] = names.map((name) => <li>{name}</li>);
return <ul>{nameList}</ul>;
}
export default IterationSample;
map을 활용하면 수많은 데이터도 위처럼 렌더링할 수 있다.
하지만 위의 코드를 렌더링하고 f12
를 눌러 개발자도구의 console을 보면 다음과 같은 에러가 발생함을 볼 수 있다. 에러를 확인해보면,
map함수를 활용하여 컴포넌트나 DOM을 반복적으로 렌더링할 때, unique한 key가 필요합니다.
즉, 각 요소에 고유의 key값을 넣어줘야한다.
근데 왜 key값을 넘겨줘야할까? 이에 대한 답은
리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내려고 사용한다. 예로 데이터를 생성, 수정, 삭제할 때 빠르게 원소의 변화를 빠르게 감지할 수 있다고 한다.
articles.map((article) => <li key={article.id}>{article.title}</li>)
예로, 페이스북과 같이 피드(게시물) 배열 데이터를 받아서 렌더링할때, 각 게시물마다 고유한 id값이 존재하므로, 위와 같이 고유한 id값을 각 요소에 key값으로 넘겨주면 해결된다.
data.map((value, index)=> <li key={index}>{value}</li>)
하지만 실습처럼 넘겨줄 고유한 id값이 없을 때, map의 callback함수의 인수인 index를 활용하여 key값을 넘겨줄 수 있다. 하지만 index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못한다고 한다. (고유한 값이 없을때만 index를 사용해야한다.)
key에 map의 index를 넣어주는 것이 리렌더링에서 비효율이라고 한다. 이를 위해 데이터를 추가할 때 고유한 id값을 넣어주려면 어떻게 해야할까? 다음과 같이 코드를 수정해보자.
id
추가uniqueId
, 데이터를 추가하기 위한 inputText
생성li
태그에 menu.id
넣어주기 function IterationSample() {
// 1.
const [menus, setMenus] = React.useState<Menus[]>([
{ id: 1, name: '짜장면' },
{ id: 2, name: '짬뽕' },
{ id: 3, name: '탕수육' },
]);
// 2.
const [uniqueId, setUniqieId] = React.useState<number>(4);
// 3.
const [inputText, setInputText] = React.useState<string>('');
// 4.
const nameList: JSX.Element[] = menus.map((menu) => <li key={menu.id}>{menu.name}</li>);
return (
<>
<h1>map으로 컴포넌트 반복하기</h1>
<input type="text" onChange={handleChange} />
<button type="button" onKeyPress={handlePress} onClick={handleClick}>
추가
</button>
<ul>{nameList}</ul>
</>
);
}
위 코드에서 함수만 아래의 함수만 추가한다.
// ...
// text 값을 저장
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
setInputText(e.target.value);
};
// 추가 버튼 클릭 시 `li`에 추가
const handleClick = (): void => {
setMenus(
menus.concat({
id: uniqueId,
name: inputText,
}),
);
setUniqieId(uniqueId + 1);
setInputText('');
};
// input에 데이터 작성 후 Enter 누르면 `li`에 추가
const handlePress = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === 'Enter') {
handleClick();
}
};
//...
F12
의 콘솔에 더이상 오류가 나타나지 않는다.import React, { ChangeEvent } from 'react';
interface Menus {
id: number;
name: string;
}
// map활용 컴포넌트 반복하기
function IterationSample() {
const [menus, setMenus] = React.useState<Menus[]>([
{ id: 1, name: '짜장면' },
{ id: 2, name: '짬뽕' },
{ id: 3, name: '탕수육' },
]);
const [uniqueId, setUniqieId] = React.useState<number>(4);
const [inputText, setInputText] = React.useState<string>('');
const handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
setInputText(e.target.value);
};
const handleClick = (): void => {
setMenus(
menus.concat({
id: uniqueId,
name: inputText,
}),
);
setUniqieId(uniqueId + 1);
setInputText('');
};
const handlePress = (e: any) => {
if (e.key === 'Enter') {
handleClick();
}
};
const nameList: JSX.Element[] = menus.map((menu) => <li key={menu.id}>{menu.name}</li>);
return (
<>
<h1>map으로 컴포넌트 반복하기</h1>
<input
type="text"
value={inputText}
onKeyPress={(e) => handlePress(e)}
onChange={handleChange}
/>
<button type="button" onClick={handleClick}>
추가
</button>
<ul>{nameList}</ul>
</>
);
}
export default IterationSample;
map
함수를 활용할 수 있다.key
값 설정에 주의해야한다. (유일한 값 !)concat
, filter
로 새로운 배열을 할당하여 새로운 상태로 설정해야한다.