html 태그 중에는 반복적으로 사용할 필요가 있는 태그(li, tr, td 등)가 있다.
// IterationSample.js
const IterationSample = () => {
return (
<ul>
<li>눈사람</li>
<li>얼음</li>
<li>눈</li>
<li>바람</li>
</ul>
);
};
이 장에서는 리액트 프로젝트에서 반복적인 내용을 효율적으로 보여주고 관리하는 방법을 알아본다.
자바스크립트 배열 객체의 내장 함수인 map 함수를 사용하면 반복되는 컴포넌트를 렌더링할 수 있다.
map 함수는 파라미터로 전달된 함수를 사용해서 배열의 각 요소를 원하는 규칙에 따라 변환한 후, 그 결과로 새로운 배열을 생성한다.
arr.map(callback, [thisArg])
이 함수의 파라미터는 다음과 같다.
callback: 새로운 배열의 요소를 생성하는 함수로, 파라미터로 다음 세 가지를 받는다.
thisArg(선택 항목): callback 함수 내부에서 사용할 this 레퍼런스
기존 배열 name을 이용해 컴포넌트로 구성된 배열을 생성해보자.
배열의 값을 사용하여 <li>태그로 둘러싸인 JSX코드로 된 배열을 새로 생성한 후, nameList에 담는다.
// IterationSample.js
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(name => <li>{name}</li>);
return <ul>{nameList}</ul>;
};
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이 없다는 경고 메시지가 뜬다.
리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아낼 때 사용한다.
key가 없으면 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교하면서 변화를 감지하는데, key가 있다면 이 값을 사용하여 더욱 빠르게 알아낼 수 있다.
key값은 배열에서 데이터를 구분하기 위해 사용하므로, 각 데이터의 고윳값을 key값으로 사용하는 것이 적절하다.
key값을 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정하면 된다.
(아래 코드에서 5번째 줄의 표현)
const articleList = articles.map(article => (
<Article
title={article.title}
writer={article.writer}
key={article.id}
/>
));
다만 앞서 만들었던 예제에는 이런 고유 번호가 없다.
이때는 map 함수에 전달되는 콜백 함수의 인수인 index 값을 사용하면 된다.
단, 데이터별로 고유한 값이 없을 때에만 index 값을 key로 사용해야 한다.
index를 key로 사용하면 배열이 변경될 때 효율적으로 리렌더링하지 못하기 때문이다.
// IterationSample.js
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map((name, index) => <li key={index}>{name}</li>);
return <ul>{nameList}</ul>;
};
지금까지는 고정된 배열을 렌더링했다. 앞으로는 동적인 배열을 렌더링해보자.
index값을 key로 사용하면 리렌더링이 비효율적이라고 하니, 각 배열 요소의 고윳값을 만드는 방법도 알아본다.
실습 초기 상태 설정 ▶ 데이터 추가 기능 구현 ▶ 데이터 제거 기능 구현
useState를 사용하여 세 가지 상태를 설정한다.
IterationSample.js 파일을 수정한다.
// IterationSample.js
import {useState} from 'react'; // useState를 사용하기 위해 임포트
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 namesList = names.map(name => <li key={name.id}>{name.text}</li>);
return <ul>{namesList}</ul>;
};
export default IterationSample;
map 함수를 사용할 때 key 값을 index 대신 name.id 값으로 지정했다.
새로운 이름을 등록할 수 있는 기능을 구현한다.
ul 태그 상단에 input과 button을 렌더링하고, input의 상태를 관리할 것이다.
<input> 태그로 받은 사용자 입력을 state 중 하나인 inputText에 저장해둔다.
// 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 namesList = names.map(name => <li key={name.id}>{name.text}</li>);
return (
<>
<input value={inputText} onChange={onChange} />
<button>추가</button>
<ul>{namesList}</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: 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 namesList = names.map(name => <li key={name.id}>{name.text}</li>);
return (
<>
<input value={inputText} onChange={onChange} />
<button onClick={onClick}>추가</button>
<ul>{namesList}</ul>
</>
);
};
export default IterationSample;
각 항목을 더블클릭했을 때 해당 항목이 화면에서 사라지는 기능을 구현한다.
이때 불변성을 유지하며 업데이트해주어야 한다. 이는 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 함을 의미한다.
불변성을 유지하면서 배열의 특정 항목을 지울 때는 배열의 내장 함수 filter를 사용한다.
* filter 함수 문법: 조건을 만족하는 요소만 모아서 리턴한다.
const numbers = [1, 2, 3, 4, 5, 6];
const biggerThanThree = numbers.filter(number => number > 3);
// 결과: [4, 5, 6]
const numbers = [1, 2, 3, 4, 5, 6];
const biggerThanThree = numbers.filter(number => number !== 3);
// 결과: [1, 2, 3, 4, 5, 6]
더블클릭할 때 사용하는 이벤트인 onDoubleClick을 사용하여 onRemove를 구현해보자.
각 li요소의 onDoubleClick 이벤트에 onRemove를 등록하자.
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 namesList = 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>{namesList}</ul>
</>
);
};
export default IterationSample;
1. 자바스크립트 배열 객체의 내장 함수인 (map) 함수를 사용하면 반복되는 컴포넌트를 렌더링할 수 있다.
2. 반복적으로 렌더링할 때 각 요소에 (key) 값이 없으면 개발자 도구에서 경고 메시지를 띄운다.
3. key값은 컴포넌트 배열을 렌더링했을 때 (어떤 원소에 변동이 있었는지)를 알아내기 위해 사용한다.
4. 배열의 각 데이터가 가진 (고윳값)을 key 값으로 설정하는 것이 적절하다.
5. 각 데이터에 (고윳값)이 없을 때에는 (map) 함수에 전달되는 (콜백 함수)의 인수인 (index) 값을 사용하면 된다. 단, 이 방법을 이용하면 배열이 변경될 때 효율적으로 리렌더링하지 못한다.
6. 리액트에서 상태를 업데이트할 때에는 (불변성)을 유지해야 한다. 기존 상태를 그대로 두면서 새로운 값을 상태로 설정해야 함을 의미한다.
7. 특정 배열에서 특정 원소만 제외시킬 수 있는 함수는 (filter) 함수이다.
1. 다음 코드를 map 함수를 사용하여 반복되는 컴포넌트를 렌더링하라. 단, 개발자 도구 콘솔이 경고를 띄우지 않게끔 처리하라.
// IterationSample.js
const IterationSample = () => {
return (
<ul>
<li>봄</li>
<li>여름</li>
<li>가을</li>
<li>겨울</li>
</ul>
);
};
2. 각 데이터에 고유 id를 추가하라.
// IterationSample.js
import {useState} from 'react';
const IterationSample = () => {
const [names, setNames] = useState([
{ text: '눈사람' },
{ text: '얼음' },
{ text: '눈' },
{ text: '바람' }
])
const [inputText, setInputText] = useState('');
const onChange = e => setInputText(e.target.value);
const onClick = () => {
const nextNames = names.concat({
text: inputText
});
setNames(nextNames); // names 값을 업데이트한다.
setInputText(''); //inputText를 비운다.
};
const namesList = names.map(name => <li key={name.id}>{name.text}</li>);
return (
<>
<input value={inputText} onChange={onChange} />
<button onClick={onClick}>추가</button>
<ul>{namesList}</ul>
</>
);
};
export default IterationSample;
Corner React Starter #2
Editor 유즈
<리액트를 다루는 기술> 8장: Hooks (0) | 2021.12.27 |
---|---|
<리액트를 다루는 기술> 7장: 컴포넌트의 라이프사이클 메서드 (0) | 2021.12.27 |
<리액트를 다루는 기술> 5장: ref: DOM에 이름 달기 (0) | 2021.11.22 |
<리액트를 다루는 기술> 4장: 이벤트 핸들링 (0) | 2021.11.15 |
<리액트를 다루는 기술> 3장: 컴포넌트 (0) | 2021.11.08 |