앞서 생성한 컴포넌트들이 실제로 작성할 수 있도록 기능을 구현해보자.
App 컴포넌트
에서 나중에 추가할 일정 항목에 대한 모든 상태들을 관리한다.src/App.js
파일을 아래의 코드로 수정하였다.//App.js
import React, { useState } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트 프로젝트 만들기',
checked: true,
},
{
id: 2,
text: '컴포넌트 스타일링 하기',
checked: true,
},
{
id: 3,
text: '기능 구현하기',
checked: false,
},
]);
return (
<TodoTemplate>
<TodoInsert />
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default App;
todos
배열 안에 들어 있는 객체
는 각 항목의 고유 id
, 내용
, 완료 여부
를 알려주는 값이 포함되어 있다.props
로 전달되며, TodoList에서 이 값을 받아서 TodoItem으로 변환하여 렌더링하도록 설정해야 한다.//TodoList.js
import React from "react";
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({ todos }) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} />
))}
</div>
);
};
export default TodoList;
props
로 받아 온 todos
배열을 배열 내장 함수 map
을 통해 TodoListItem으로 이루어진 배열로 변환하여 렌더링했다.map
을 사용하여 컴포넌트로 변환할 때는 key props
를 전달해주어야 함.❗❗key
값은 각 항목마다 고윳값인 id
를 넣어주고, todo
데이터는 통째로 props
로 전달했는다.객체
를 통째로 전달하는게 성능 최적화할 때 편하다고 한다.todo
값에 따라 UI
를 보여주기 위해 TodoListItem.js
를 다음과 같이 수정했다.//TodoListItem.js
import React from "react";
import {
MdCheckBox,
MdRemoveCircleOutline,
MdCheckBoxOutlineBlank,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({ todo }) => {
const { text, checked } = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})}}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove">
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
classnames
를 사용했다.todos
값에 따라 내용을 보여주는 것을 확인할 수 있다.인풋 상태
를 관리하고 App 컴포넌트에 todos
배열에 새로운 객체를 추가했다.인풋
에 입력하는 값을 관리하기 위해 useState
를 사용하여 value
라는 상태를 정의했다.인풋
에 넣어 줄 onChange
함수를 생성하는데 컴포넌트가 리렌더링될 때마다 함수를 생성하지 않도록 주의❗❗//TodoInsert.js
import React, { useState, useCallback } from "react";
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = ({onInsert}) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
return (
<form className="TodoInsert">
<input
placeholder="할 일을 입력하세요"
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
todos
배열에 새로운 객체를 추가하는 onInsert
함수를 생성해야 한다.객체
를 만들 때마다 고윳값인 id를 1씩 더해 주어야 하는데, id 값은 useRef
를 사용하여 관리했다.useState
가 아닌 useRef
를 사용하는 이유는 id
값은 렌더링되는 정보가 아니기 때문임❗❗onInsert
함수 또한 useCallback
으로 감싸주었다.props
로 전달해야 할 함수는 useCallback
을 사용하여 함수를 감싸는 것을 습관화 하자.❗❗//App.js
import React, { useState, useRef, useCallback } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '리액트 프로젝트 만들기',
checked: true,
},
{
id: 2,
text: '컴포넌트 스타일링 하기',
checked: true,
},
{
id: 3,
text: '기능 구현하기',
checked: false,
},
]);
// 고유값으로 사용될 id
// ref를 사용하여 변수 담기
const nextId = useRef(4);
const onInsert = useCallback(text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
setTodos(todos.concat(todo));
nextId.current += 1; // id 값 1씩 증가
}, [todos]);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default App;
버튼
을 클릭하면 App에서 TodoInsert에 넣어 준 onInsert
함수에 현재 useState
를 통해 관리하고 있는 value
값을 파라미터로 넣어서 호출하는 함수를 만들어 보자.//TodoInsert.js
import React, { useState, useCallback } from "react";
import { MdAdd } from 'react-icons/md';
import './TodoInsert.scss';
const TodoInsert = ({onInsert}) => {
const [value, setValue] = useState('');
const onChange = useCallback(e => {
setValue(e.target.value);
}, []);
const onSubmit = useCallback(e => {
onInsert(value);
setValue(''); // value 값 초기화
// submit 이벤트가 브라우저를 새로고침하는 것을 방지
e.preventDefault();
}, [onInsert, value]);
return (
<form className="TodoInsert" onSubmit={onSubmit}>
<input
placeholder="할 일을 입력하세요"
value={value}
onChange={onChange}
/>
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
onSubmit
이라는 함수를 만들고, 이를 form
의 onSubmit
으로 설정했다.onSubmit
이벤트의 경우 Enter
를 눌렀을 때도 이벤트가 발생하기 때문이다.✔onSubmit
함수가 호출되면 props
로 받아 온 onInsert
함수에 현재 value
값을 파라미터로 넣어서 호출하고, 현재 value
값을 초기화한다.onSubmit
이벤트는 브라우저를 새로고침시키는데, 위의 코드와 같이 e.preventDefault()
함수를 호출하면 새로고침을 방지할 수 있다.filter
를 사용하면 된다.filter
함수에는 조건을 확인해 주는 함수를 파라미터로 넣어주고, true
를 반환하는 경우만 새로운 배열에 포함된다.filter
함수를 사용하여 onRemove
함수를 생성했다.//App.js
import React, { useState, useRef, useCallback } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
(...)
const onRemove = useCallback(id => {
setTodos(todos.filter(todo => todo.id !== id));
}, [todos]);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} />
</TodoTemplate>
);
};
export default App;
onRemove
함수는 App 컴포넌트에 id
를 파라미터로 받아 와서 같은 id
를 가진 항목을 todos
배열에서 지우는 함수이다.props
로 설정했다.onRemove
함수를 사용하려면 TodoList 컴포넌트를 거쳐야 한다.props
로 받아 온 onRemove
함수를 TodoListItem에 전달하면 된다.//TodoList.js
import React from "react";
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({ todos, onRemove }) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</div>
);
};
export default TodoList;
onRemove
함수에 현재 자신이 가진 id
를 넣어서 삭제 함수를 호출하도록 설정해야 한다.//TodoListItem.js
import React from "react";
import {
MdCheckBox,
MdRemoveCircleOutline,
MdCheckBoxOutlineBlank,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({ todo, onRemove }) => {
const { id, text, checked } = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})}}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
onToggle
이라는 함수를 App에 만들고, 해당 함수를 TodoList 컴포넌트에 props
로 넣어 사용해야 한다.//App.js
import React, { useState, useRef, useCallback } from 'react';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
const App = () => {
(...)
const onToggle = useCallback(id => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, checked: !todo.checked } : todo ));
}, [todos]);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
</TodoTemplate>
);
};
export default App;
map
을 사용하여 특정 id
를 가지고 있는 객체
의 check 값을 반전시켜 주고있다.map
함수를 사용한 이유는 id
값을 비교하여 정해 준 규칙대로 새로운 객체
를 생성 하지만, id
값이 다를 때는 변화를 주지 않고 처음 상태를 반환해야 하기 때문이다.onToggle
함수를 TodoListItem에서 호출할 수 있도록 TodoList를 거쳐 TodoListItem에 전달하도록 구현했다.//TodoList.js
import React from "react";
import TodoListItem from "./TodoListItem";
import './TodoList.scss';
const TodoList = ({ todos, onRemove, onToggle }) => {
return (
<div className="TodoList">
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove}
onToggle={onToggle}/>
))}
</div>
);
};
export default TodoList;
//TodoListItem.js
import React from "react";
import {
MdCheckBox,
MdRemoveCircleOutline,
MdCheckBoxOutlineBlank,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
const TodoListItem = ({ todo, onRemove, onToggle }) => {
const { id, text, checked } = todo;
return (
<div className="TodoListItem">
<div className={cn('checkbox', {checked})} onClick={() => onToggle(id)}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => onRemove(id)}>
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
첫 토이 프로젝트를 진행했는데, 정말 누가봐도 기본적이고 소규모 프로젝트라서 따로 컴포넌트 리렌더링 최적화 작업을 하지 않아도 잘 작동한다.
하지만 일정
의 개수가 많이 생겼을 경우 기존 항목을 삭제
및 토글
할 때 지연될 수 있을 것 같다.❗❗
다음엔 Todo-List 프로젝트를 활용하여 클라이언트 자원
을 더욱 효과적으로 사용하기 위해 불필요한 리렌더링을 방지하는 방법에 대해 살펴보고 실습해보고자 한다.😊😊