2023.02.06
지난시간 아래와 같은 화면을 구현했었다.

import React from "react";
const App = () => {
const style = {
padding: "100px",
display: "flex",
gap: "12px",
};
const squareStyle = {
width: "100px",
height: "100px",
border: "1px solid green",
borderRadius: "10px",
display: "flex",
alignItems: "center",
justifyContent: "center",
};
return (
<div style={style}>
<div style={squareStyle}>감자</div>
<div style={squareStyle}>고구마</div>
<div style={squareStyle}>오이</div>
<div style={squareStyle}>가지</div>
<div style={squareStyle}>옥수수</div>
</div>
);
};
export default App;
하지만 이렇게 하는 것은 같은 코드가 여러 번 중복이된다. “감자", “고구마" 와 같은 이름들도 개발자가 직접 입력을 해줘야 하고, 데이터로서 관리가 되고 있지 않다. 만약에 “토마토" 라는 항목이 추가되면 개발자는 화면을 추가로 개발해줘야 한다.
바로 map()(자바스크립트 메서드)을 사용하는 것 이다.
map() 을 사용하기 위해 채소들의 이름을 배열로 만들어보자.
// src/App.js
function App(){
// .. 중략
const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"];
return <div></div>
}
// .. 중략
그리고 JSX 부분에서 아래와 같이 작성한다.
import React from "react";
const vegetables = ["감자", "고구마", "오이", "가지", "옥수수"];
return (
<div className="app-style">
{vegetables.map((vegetableName) => {
return (
<div className="square-style" key={vegetableName}>
{vegetableName}
</div>
);
})}
</div>
);
};
export default App;
JSX 부분에서 map() 즉, 자바스크립트 코드를 작성할 것 이기때문에 { } 로 먼저 감싸고 시작한다.
JSX에서 map() 은 배열의 모든 요소를 순회한다. 그래서 클라이언트에서는 배열 형태의 데이터를 활용해서 화면을 그려주는 경우가 많고, 이때 배열의 값들로 동적으로 컴포넌트를 만들수 있다.
map을 사용하니 중복된 코드가 사라지고 1개의 컴포넌트를 이용하면서 그 안에서 <div>{vegetableName}</div> 가 순차적으로 보여지고 있다.
조금 더 복합한 구조의 데이터를 다뤄보자. 주어진 정보는 다음과 같다. user 라는 정보이며 배열안에 object literal 형태의 데이터가 있다.
const users = [
{ id: 1, age: 30, name: "송중기" },
{ id: 2, age: 24, name: "송강" },
{ id: 3, age: 21, name: "김유정" },
{ id: 4, age: 29, name: "구교환" },
];
App 컴포넌트에서는 user.map() 을 통해 user의 정보를 순회하고 각각의 user 정보를 User 컴포넌트로 주입해줍니다.
import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div>{props.user.age}살 - {props.user.name}</div>
);
}
const App = () => {
const users = [
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
];
return (
<div className="app-container">
{users.map((user) => {
return <User user={user} key={user.id} />;
})}
</div>
);
};
export default App;
User 컴포넌트에 props로 들어오는 user의 정보는 { id: 1, age: 30, name: "송중기" } 라는 정보가 들어오는 것이다.
map() 의 기능을 이용해서 우리는 앞으로 반복되는 컴포넌트를 간단하게 화면에 표시할 수 있다.
리액트에서 동적으로 변화는 데이터는 state라는 상태 값으로 관리한다. 기존 정적 배열 데이터로 관리했던 유저 리스트를 useState를 활용해 변화가 일어나면 리랜더링을 초래하는 ‘상태 값’으로 만들어보자.
//기존 users 배열
const users = [
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
];
//useState를 이용한 상태값 만들기
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
여기에서 user를 추가하고, 삭제하는 기능을 추가할 것이다. 그것을 위한 UI를 작성한다.
import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div>{props.user.age}살 - {props.user.name}</div>
);
}
const App = () => {
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
const [age, setAge] = useState('');
return (
<div className="app-container">
<input value={name}
placeholder="이름을 입력해주세요"
// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
<input value={age}
placeholder="나이를 입력해주세요"
// 인풋 이벤트로 들어온 입력 값을 age의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
{users.map((user) => {
return <User user={user} key={user.id} />;
})}
</div>
);
};
export default App;
import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div className="user-card">
<div>{props.user.age}살 - </div>
<div>{props.user.name}</div>
</div>
);
}
const App = () => {
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
const addUserHandler = () => { // -- 유저추가 핸들러 기능
const newUser = {
id: users.length + 1, // -- 좋은 코드는 아니므로 변경예정
age: age,
name: name,
};
setUsers([...users, newUser]);
};
return (
<div className="app-container">
<input
value={name}
// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
{users.map((user) => {
return <User user={user} key={user.id} />;
})}
<button onClick={addUserHandler}>추가하기</button>
</div>
);
};
export default App;
import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div className="user-card">
<div>{props.user.age}살 - </div>
<div>{props.user.name}</div>
<button onClick={() => props.handleDelete(props.user.id)}>
삭제하기
</button>
</div>
);
}
const App = () => {
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
const addUserHandler = () => {
const newUser = {
id: users.length + 1,
age: 30,
name: name,
};
setUsers([...users, newUser]);
};
const deleteUserHandler = (id) => {
const newUserList = users.filter((user) => user.id !== id);
setUsers(newUserList); // filter를 이용해 예외를 지정후 다시돌려줌
};
return (
<div className="app-container">
<input
placeholder="이름을 입력해주세요"
value={name}
// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
{users.map((user) => {
return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
})}
<button onClick={addUserHandler}>추가하기</button>
</div>
);
};
export default App;
이런식으로 버튼과 기능들이 많이지다보면 복잡해지고, 재사용하기가 힘들다. 이러한 부분들을 재사용하기위해 버튼을 컴포넌트로 분리한다.
import React from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
//1. 버튼 컴포넌트 생성
function CustomButton(props) {
return <button onClick={props.onClick}>{props.children}</button>;
}
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div className="user-card">
<div>{props.user.age}살 - </div>
<div>{props.user.name}</div>
//2. 버튼을 컴포넌트로 바꾸기
<CustomButton onClick={() => props.handleDelete(props.user.id)}>
삭제하기
</CustomButton>
</div>
);
}
const App = () => {
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
const addUserHandler = () => {
const newUser = {
id: users.length + 1,
age: 30,
name: name,
};
setUsers([...users, newUser]);
};
const deleteUserHandler = (id) => {
const newUserList = users.filter((user) => user.id !== id);
setUsers(newUserList);
};
return (
<div className="app-container">
<input
placeholder="이름을 입력해주세요"
value={name}
// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
{users.map((user) => {
return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
})}
//3. 버튼을 컴포넌트로 바꾸기
<CustomButton onClick={addUserHandler}>추가하기</CustomButton>
</div>
);
};
export default App;
이렇게 컴포넌트를 따로 분리해주면, 버튼 컴포넌트를 호출할 때 색상에 대한 정보를 props로 전달함으로써 추가하기 버튼은 초록색으로 삭제하기 버튼은 빨강색으로 쉽게 변경할수 있습니다.
// Button 컴포넌트 부분만
function CustomButton(props) {
const {color, onClick, children} = props
if (color)
return (
<button
style={{ background: color, color: "white" }}
onClick={onClick}
>
{children}
</button>
);
return <button onClick={onClick}>{props.children}</button>;
}
<Button color="red" onClick={() => props.handleDelete(props.user.id)}>삭제하기</Button>
<Button color="green" onClick={handleAdd}>추가하기</Button>
현재 App컴포넌트와 User컴포넌트, Button컴포넌트가 모두 App.js라는 파일 한 곳에 작성되어 있기 때문에 발생하는 몇 가지 문제들이 있다.
때문에 기능을 재사용 하는 컴포넌트들을 컴포넌트끼리 폴더로 묶어서 따로 관리해서 사용하는것이 좋다.

(예시 이미지)
버튼 컴포넌트를 따로 독립시켜주었다.
// 경로: src/components/Button.js
function Button(props) {
switch (props.color) {
case 'green': {
return (
<button
style={{ background: 'green', color: 'white' }}
onClick={props.onClick}
>
{props.children}
</button>
);
}
case 'red': {
return (
<button
style={{ background: 'red', color: 'white' }}
onClick={props.onClick}
>
{props.children}
</button>
);
}
default: {
return <button onClick={props.onClick}>{props.children}</button>;
}
}
}
export default Button;
// 💡💡 외부 모듈(파일)에서 Sqaure 컴포넌트를 사용할 수 있게 export(내보내기)해줘야 한다.
이후는 기존의 App.js
// 경로: src/App.js
import React from 'react';
import Button from './components/Button.js';
// User 컴포넌트를 분리해서 구현
function User(props) {
return (
<div className="user-card">
<div>{props.user.age}살 - </div>
<div>{props.user.name}</div>
//2. 버튼을 컴포넌트로 바꾸기
<Button onClick={() => props.handleDelete(props.user.id)}>
삭제하기
</Button>
</div>
);
}
const App = () => {
const [users, setUsers] = useState([
{ id: 1, age: 30, name: '송중기' },
{ id: 2, age: 24, name: '송강' },
{ id: 3, age: 21, name: '김유정' },
{ id: 4, age: 29, name: '구교환' },
]);
const [name, setName] = useState(''); // <-- 유저의 입력값을 담을 상태
const addUserHandler = () => {
const newUser = {
id: users.length + 1,
age: 30,
name: name,
};
setUsers([...users, newUser]);
};
const deleteUserHandler = (id) => {
const newUserList = users.filter((user) => user.id !== id);
setUsers(newUserList);
};
return (
<div className="app-container">
<input
placeholder="이름을 입력해주세요"
value={name}
// 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
onChange={(e) => setName(e.target.value)}
/>
{users.map((user) => {
return <User user={user} key={user.id} handleDelete={deleteUserHandler}/>;
})}
//3. 버튼을 컴포넌트로 바꾸기
<Button onClick={addUserHandler}>추가하기</Button>
</div>
);
};
export default App;
리액트에서 map을 사용하여 컴포넌트를 반복 렌더링 할 때는 반드시 컴포넌트에 key를 넣어줘야 한다. key가 필요한 이유는 React에서 컴포넌트 배열을 렌더링했을 때 각각의 원소에서 변동이 있는지 알아내려고 사용하기 때문이다. 만약 key가 없다면 React는 가상돔을 비교하는 과정에서 배열을 순차적으로 비교하면서 변화를 감지하려 하지만, key가 있으면 이 값을 이용해서 어떤 변화가 일어났는지 더 빠르게 알아낼 수 있게 된다.
즉, key값을 넣어줘야 React의 성능이 더 최적화 된다.
key는 아래와 같이 넣어준다.
<div style={style}>
{users.map((user) => {
return <User user={user} key={user.id} />;
})}
</div>
간혹 map((value, index)=>{}) 처럼, map에서 지원해주는 index를 사용해서 key를 넣는 경우가 있는데, 이것은 좋지 않은 방식이지 최대한 지양해주자.