웹 애플리케이션을 만들 때 종종 반복되는 코드를 작성하는 경우가 생긴다. 아래 코드를 보자
// src/IterationSample.tsx
function IterationSample() {
return (
<ul>
<li>눈사람</li>
<li>얼음</li>
<li>눈</li>
<li>바람</li>
</ul>
);
}
export default IterationSample;
위 코드에서 특정 형태가 반복되는 모습을 확인할 수 있다. 현재는 간단한 코드이지만, 더욱 큰 프로젝트에서 더 복잡해진 코드를 봐야한다면, 반복적인 내용을 효율적으로 관리하는 방법이 있어야 한다.
map()
map함수를 사용해 반복되는 컴포넌트를 렌더링할 수 있다. map함수는 파라미터로 전달된 함수를 사용해서 배열 내 각 요소를 원하는 규칙에 따라 변환한 후 그 결과로 새로운 배열을 생성한다.
문법
arr.map(callback(currentValue[, index[, array]])[, thisArg])
- callback : 새로운 배열의 요소를 생성하는 함수
- curruntValue : 현재 처리하고 있는 요소
- index : 현재 처리하고 있는 요소의 index 값
- array : 현재 처리하고 있는 원본 배열
- thisArg(optional) : callback 함수 내부에서 사용할 this 레퍼런스
map() 함수는 새로운 배열을 반환하며, 기존의 배열은 변경하지 않는다.
const arr = [1, 2, 3];
const squaredArr = arr.map(x => x * x);
console.log(squaredArr); // [1, 4, 9]
위 코드는 배열의 모든 요소에 대해 제곱 값을 계산한 새로운 배열을 만드는 방법이다.
데이터 배열을 컴포넌트 배열로 변환
그렇다면, 맨 처음 코드에 map()을 적용시켜보자.
function IterationSample() {
const names = ["눈사람", "얼음", "눈", "바람"];
const nameList = names.map((name) => <li>{name}</li>);
return <ul>{nameList}</ul>;
}
export default IterationSample;
이런식으로 작성할 수 있다. 리스트의 요소를 줄이기와 늘리기에도 이전 코드보다 편하다.
이 코드를 App 컴포넌트를 통해 렌더링하면 아래와 같은 결과가 나온다.
겉으로는 잘 나온것 같지만, 브라우저의 검사 페이지를 열어 콘솔을 확인해보면 경고 메세지가 표시되고 있다.
"key"와 prop이 없다는 경고 메세지를 표시하고있다.
key
리액트에서 key는 각 요소의 고유한 식별자(identifier)로 사용된다. key는 props 객체 내부에 위치하며, 컴포넌트가 렌더링 될 때 리액트가 각 요소를 구별하기 위해 사용된다. 또한, 어떤 원소에 변동이 있었는지도 이를 통해 확인할 수 있다.
key를 사용하는 이유는 크게 두 가지로 설명할 수 있다.
- 성능 최적화
리액트에서 컴포넌트를 렌더링할 때, key를 사용해 이전에 렌더링된 요소와 새로운 요소를 구별한다. 이를 통해 리액트는 변경된 요소만 업데이트하고, 변경되지 않은 요소는 그대로 유지해 성능을 최적화할 수 있다.
- 상태 관리
리스트 컴포넌트에서 key를 사용하면, 각 요소들의 상태를 관리하기 쉬워진다. 예를 들어, 리스트에서 특정 요소를 삭제하면, 해당 요소의 key를 사용해 쉽게 삭제할 수 있다.
key는 보통 배열의 인덱스나 유일한 식별자를 사용한다. 하지만 인덱스를 key로 사용하는 것은 권장되지 않는 방법인데, 배열의 요소가 추가되거나 삭제될 경우 인덱스가 변경되기 때문에, 리액트가 요소를 잘못 비교할 수 있기 때문이다. 따라서, 가능하다면 요소의 고유한 식별자를 key로 사용하는 것이 좋다.
따라서 위 코드를 key를 사용한 코드로 변경해보자.
function IterationSample() {
const names = ["눈사람", "얼음", "눈", "바람"];
const nameList = names.map((name, index) => <li key={index}>{name}</li>);
return <ul>{nameList}</ul>;
}
export default IterationSample;
이 코드를 다시 렌더링해서 브라우저를 검사해보면, 콘솔에서 경고가 뜨지 않음을 확인할 수 있다.
현재는 예시로 인덱스를 사용했지만, 이는 최후의 수단으로 사용하는 것이 좋다. 리스트에 고유한 식별자가 있다면 그 식별자를 key로 사용하는것이 가장 좋다.
동적 배열 렌더링
지금까지는 고정 배열에 대한 렌더링을 반복자로 구현해보았다. 이제부터는 동적인 배열에 대해 렌더링하는것을 구현해보고자 한다. 또한, 인덱스를 key로 사용하는것은 리렌더링시 비효율적이기 때문에, 식별자도 만들어보자.
초기 상태 설정
IterationSample 컴포넌트에서 useState를 사용해 상태를 설정해주려 한다. 세 가지 상태를 설정하는데, 각각 데이터 배열, 텍스트를 입력할 수 있는 input의 상태, 고유 식별자 id를 설정할 것이다.
이번에 만들어줄 배열은 객체 형태로 이루어진 배열이다. 해당 객체에는 고유 id 값이 있다.
import { useState } from "react";
function 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 값으로 지정해주었다.
렌더링시 같은 결과가 표시된다.
데이터 추가 기능 구현
이제, 입력을 통해 데이터를 추가할 수 있는 기능을 구현해보자. 기능 구현 목록은 다음과 같다.
- ul태그 상단에 input과 button을 렌더링한다.
- 버튼을 클릭했을 때 호출될 onClick 함수를 구현한다.
import { useState } from "react";
function 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
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setInputText(e.target.value);
}
function onClick() {
const nextNames = names.concat({
id: nextId,
text: inputText,
});
setNextId(nextId + 1);
setNames(nextNames);
setInputText("");
}
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;
두 가지의 기능이 추가된 코드이다. 이 코드를 렌더링해서 데이터를 추가해보았다.
원하는 동작이 잘 수행되는 것을 확인할 수 있다.
먼저 input과 button은 구현이 가능하다. onClick 함수는 '추가' 버튼을 클릭했을 때 호출되며, 현재 inputText값을 새로운 항목으로 추가하기 위해 names 배열에 concat 메소드를 사용해 새로운 배열을 생성한다. 그리고 setNames함수를 호출해 names의 state를 새로운 배열로 업데이트 한다. 그리고 nextId의 state를 1 증가시켜 새로운 항목에 사용할 식별자로 값을 업데이트 하고, setInputText 함수를 호출해 inputText 상태를 초기화한다.
데이터 제거 기능 구현
이번에는 데이터를 제거하는 기능을 구현해보자. 각 항목을 더블클릭 했을 때 해당 항목이 화면에서 사라지는 기능을 구현한다. 이번에도 마찬가지로 불변성을 유지하면서 업데이트 해줘야 한다. 이 경우, filter 함수를 사용한다.
filter 함수는 특정 조건을 만족하는 항목들만 추출해 새로운 배열을 생성하는 함수이다. 예시를 보자.
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers); //[2, 4]
numbers라는 배열에서 짝수만 추출하여 새로운 배열에 생성하는 코드이다. filter함수 또한 기존 배열을 건드리지 않고 새로운 배열을 생성한다. 리액트에서 filter 함수는 주로 배열 데이터를 랜더링할 때 특정 조건에 따라 렌더링할 데이터를 선택하는데 사용된다. 또한, 타입스크립트를 사용해 filter 함수를 사용할 때에는 제네릭을 이용해 반환되는 배열의 타입을 명시할 수 있다.
이제 filter함수를 이용해서 컴포넌트 항목 제거 기능을 구현해보자. HTML 요소를 더블클릭할 때 사용하는 이벤트 이름은 onDoubleClick이다. onRemove라는 함수를 만들어서 각 li 요소에 이벤트 등록을 해보자.
import { useState } from "react";
function 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
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
setInputText(e.target.value);
}
function onClick() {
const nextNames = names.concat({
id: nextId,
text: inputText,
});
setNextId(nextId + 1);
setNames(nextNames);
setInputText("");
}
function onRemove(id: number) {
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;
onRemove 함수는 li 요소를 더블클릭할 때 호출되는 함수이다. filter를 사용해, 기존의 names 배열에서 삭제할 id 값을 가진 객체를 제외한 새로운 배열을 생성한다. 이후, setNames 함수를 사용해 names 변수의 값을 새로운 배열로 업데이트 한다.
리렌더링한 결과이다. 원하는 대로 요소를 더블클릭 했을 때 잘 지워지는 것을 확인할 수 있다.
'WEB > React' 카테고리의 다른 글
리액트 라이프사이클 메서드 사용하기 [react/life cycle/typescript] (0) | 2023.03.13 |
---|---|
리액트 라이프사이클 이해하기 [react/life cycle/typescript] (0) | 2023.03.13 |
리액트 DOM 네이밍 이해하기 (0) | 2023.03.12 |
리액트 이벤트 시스템 이해하기 [react/event/typescript] (0) | 2023.03.11 |
리액트 컴포넌트 이해하기 [react/component/typescript] (0) | 2023.03.11 |