웹 애플리케이션을 만들다 보면 코드를 반복하여 사용할 때가 있다. 반복적인 코드를 효율적으로 보여주고 관리하는 방법을 배워보겠다.
자바스크립트 배열 객체의 내장 함수인 map함수를 이용해볼 수 있다. map 함수는 배열의 각 요소를 원하는 규칙에 따라 변환한 후, 그 결과로 새로운 배열을 생성한다.
arr.map(callback, [thisArg])
var numbers = [1, 2, 3, 4, 5];
var processed = numbers.map(function(num){
return num * num;
// var processed = numbers.map(num => num*num);
console.log(processed);
map함수를 이용하여 각 요소를 제곱할 수 있는 함수를 넣어 processed라는 새로운 배열을 만들어냈다.
문자열로 구성된 name 배열을 만들어 map함수를 이용하여 nameList라는 새로운 배열을 만들어낸다.
// IterationSample.js
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map(name => <li>{name}</li>);
return <ul>{nameList}</ul>;
};
export default IterationSample;
App 컴포넌트에서 IterationSample 컴포넌트를 불러와 렌더링한다.
// App.js
import { Component } from 'react';
import IterationSample from './IterationSample';
class App extends Component {
render() {
return (
<IterationSample />
);
}
}
export default App;
웹브라우저를 확인하면 원하는 대로 렌더링이 되었으나 크롬 개발자 도구의 콘솔을 확인하면 "key" prop이 없다는 경고 메시지가 뜬다.
리액트에서 key는 컴포넌트 배열을 렌더링했을 때 어떤 원소에 변동이 있었는지 알아내기 위해 사용이 된다. 물론 key가 없다면 Virtual DOM을 비교하는 과정에서 리스트를 순차적으로 비교할 것이다. 하지만 key가 있다면 어떤 변화가 일어났는지 빠르게 알아낼 수 있다.
key값을 설정할 때는 map 함수의 인자로 전달되는 함수 내부에서 컴포넌트 props를 설정하듯이 설정하면 된다. key의 값은 언제나 유일해야 하기 때문에 각 데이터의 고윳값을 key값으로 사용하는 것이 적절하다.
const articleList = articles.map(article => (
<Article
title={article.title}
writer={article.writer}
key={article.id}
/>
));
하지만 key가 없는 경우도 있다. 앞서 봤던 예제가 그 예이다. 이럴떈 index을 이용해볼 수 있다.
// IterationSample.js
const IterationSample = () => {
const names = ['눈사람', '얼음', '눈', '바람'];
const nameList = names.map((name, index) => <li key={index}>{name}</li>);
return <ul>{nameList}</ul>;
};
export default IterationSample;
단 데이터별로 고유한 값이 없을 때만 index을 활용해야 한다. index을 key로 이용하게 되면 배열이 변경될 때 효율적으로 리렌더링하지 못하기 때문이다.
지금까지는 고정된 배열을 렌더링 했다. 이제는 동적인 배열을 렌더링을 구현하는 것을 알아보겠다.
// 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 namesList = names.map(name => <li key={name.id}>{name.text}</li>);
return <ul>{namesList}</ul>;
};
export default IterationSample;
새로운 이름을 등록할 수 있는 기능을 구현해보자.
ul 태그 상단에 input과 button을 렌더링하고, input 상태를 관리한다.
// 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 이벤트에 설정한다.
// 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;
코드를 보면 배열에 추가할 때 push가 아닌 concat를 사용한 것을 볼 수 있는데, push는 기존 배열 자체를 변경해주는 반면 concat는 새로운 배열로 바꿔주기에 concat을 사용했다.
리액트에서 컴포넌트의 성능을 최적화하기 위해서는 어떤 값을 업데이트를 할 때 기존 상태를 두면서 새로운 값의 상태로 설정해야 한다. 이를 불변성 유지라고 한다. 그 이유는 뒤에서 다뤄보겠다.
각 항목을 더블클릭했을 때 해당 항목이 화면에서 사라지는 기능을 구현해보자. 위에서 concat를 사용한 것처럼 불변성을 유지해야 하기에 이번에도 배열의 내장 함수인 filter를 사용하여 구현하겠다.
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;
// IterationSample.js
const IterationSample = () => {
const weather = ['맑음', '흐림', '비', '눈'];
const weatherList = weather.map(w => <li>{w}</li>);
return <ul>{weatherList}</ul>;
};
export default IterationSample;
// 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);
setInputText('');
};
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;
정답
1. map함수
2. O
3. 고윳값
4. 고윳값, index
5. 불변성 유지
6. O
7. filter
8.
// IterationSample.js
const IterationSample = () => {
const weather = ['맑음', '흐림', '비', '눈'];
const weatherList = weather.map((w, index) => <li key={index}>{w}</li>);
return <ul>{weatherList}</ul>;
};
export default IterationSample;
// 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 onChange = e => setInputText(e.target.value);
const onClick = () => {
const nextNames = names.concat({
text: inputText
});
setNames(nextNames);
setInputText('');
};
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;
[리액트스타터2] 8장. Hooks (0) | 2022.11.24 |
---|---|
[리액트스타터2] 7장. 컴포넌트의 라이프사이클 메서드 (0) | 2022.11.17 |
[리액트스타터2] 5장. ref (0) | 2022.11.05 |
[리액트스타터2] 4장. 이벤트 핸들링 (0) | 2022.10.13 |
[리액트스타터2] 3장. 컴포넌트 (0) | 2022.10.06 |