이번 10장에서는 컴포넌트의 일정관리 웹 애플리케이션에 대해서 알아보고자 합니다.
지금까지 배운 이론을 바탕으로 실제로 사용 가능한 웹 용 애플리케이션을 만들어 봅시다.
이전의 이론을 바탕으로 애플리케이션을 생성하기에 앞서, 라이브러리 설치 및 기타 프로그램 설정과 생성이 필요합니다. 이를 단계별로 나누어 알아봅시다.
명령프롬포트 창 등을 통해 라이브러리를 설치하면 되는데, 설치할 라이브러리는 우선 3가지입니다.
2장에서 Prettier에 대해 학습했었습니다. 이를 참고해서 프로젝트를 만들 수 있습니다.
지금까지 제작한 프로젝트 최상위 디렉터리에 [.prettierrc] 파일 생성합니다.
생성 내용은 아래의 코드와 같습니다.
// [.prettierrc] 파일 내용
{
“singleQuote“: true,
“semi“: true,
“useTabs“: false,
“tabWidth“: 2,
“trailingComma“: “all“,
“printWidth“: 80
}
기존 설정을 지우고 배경을 회색으로 만드는 아스키코드(#e9ecef)를 넣어 아래와 같이 수정합니다.
// [index.css] 파일 내용
body {
margin: 0;
padding: 0;
background: #e9ecef;
}
아래의 코드와 같이 컴포넌트의 기본 틀을 잡는 파일로 수정해 줍니다.
// [App.js] 파일 내용
import React from 'react';
const App = () => {
return <div>Todo App 생성 프로젝트!</div>;
};
export default App;
이렇게 기본적인 설정 및 개발 준비가 끝났습니다.
다음으로, UI를 만들어 봅시다.
우리가 만들 컴포넌트는 각각의 기능별로 구분했을 때 총 4가지입니다. 관습상 [ src > components ] 디렉터리를 생성해 이를 저장합니다.
여기 네 개의 템플릿을 생성한 후, App.js에 이를 불러와 렌더링 하면 UI 구성이 끝납니다.
다음으로, 각각의 템플릿에 작성할 코드에 대해 알아봅시다.
js파일과 scss 파일 두 가지를 생성합니다.
[TodoTemplate.js] 파일은 아래의 코드와 같습니다.
// [TodoTemplate.js] 파일 생성
import React from ‘react‘;
import ‘./TodoTemplate.scss‘;
const TodoTemplate = ({ children }) => {
return (
<div className=“TodoTemplate“>
<div className=“app-title“>일정 관리</div>
<div className=“content“>{children}</div>
</div>
);
};
export default TodoTemplate;
[TodoTemplate.scss] 파일은 아래와 같습니다.
// [TodoTemplate.scss] 파일 생성
.TodoTemplate {
width: 512px;
margin-left: auto;
margin-right: auto;
margin-top: 6rem;
border-radius: 4px;
overflow: hidden;
.app-title {
background: #22b8cf;
color: white;
height: 4rem;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.content {
background: white;
}
}
flex에 대한 자세한 설명 참고 사이트 : |
다음으로, 앞서 생성한 [App.js] 파일을 불러와 만든 [TodoTemplate.js]를 랜더링 하는 코드를 추가합니다. 이는 모든 4개의 템플릿을 생성할 때마다 추가해야 하므로, 맨 마지막에 코드를 정리하겠습니다.
자동 import 문 작성하기 * 추가하려는 컴포넌트가 탭으로 열려있는 경우 : 자동 생성
* 추가하려는 컴포넌트가 탬으로 열려있지 않는 경우 : 프로젝트 최상위 디렉터리에 [ jsconfig.json ] 파일 생성. (ctrl+space, enter 키 입력으로 자동 생성 가능) |
// [jsconfig.json] 파일.
{
"compilerOptions": {
"target": "es6"
}
}
2) 다음으로, TodoInsert 코드를 생성합니다.
마찬가지로, js와 scss 파일을 생성합니다.
TodoInsert.js 코드는 아래와 같습니다.
// [TodoInsert.js] 파일 생성
import React from 'react';
import { MdAdd } from 'react-icons/md'; //
import './TodoInsert.scss';
const TodoInsert = () => {
return (
<form className="TodoInsert">
<input placeholder="할 일을 입력하십시오" />
<button type="submit">
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
react-icons 에 대한 자세한 설명 참고 사이트 : |
TodoInsert.scss 소스코드는 아래와 같습니다.
// [TodoInsert.scss] 파일 생성
.TodoInsert {
display: flex;
background: #495057;
input {
// 기본 스타일 초기화
background: none;
outline: none;
border: none;
padding: 0.5rem;
font-size: 1.125rem;
line-height: 1.5;
color: white;
&::placeholder {
color: #dee2e6;
}
flex: 1;
}
button {
// 기본 스타일 초기화
background: none;
outline: none;
border: none;
background: #868e96;
color: white;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.5rem;
display: flex;
align-items: center;
cursor: pointer;
transition: 0.1s background ease-in;
&:hover {
background: #adb5bd;
}
}
}
App.js에서 TodoTemplate를 불러온 것과 같이, [TodoInsert.js]를 불러와 렌더링 합니다.
TodoListItem.js의 소스 코드는 아래와 같습니다.
// [TodoListItem.js] 파일 생성
import React from ‘react‘;
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} from ‘react-icons/md‘;
import ‘./TodoListItem.scss‘;
const TodoListItem = () => {
return (
<div className=“TodoListItem“>
<div className=“checkbox“>
<MdCheckBoxOutlineBlank />
<div className=“text“>할 일</div>
</div>
<div className=“remove“>
<MdRemoveCircleOutline />
</div>
</div>
);
};
export default TodoListItem;
scss의 소스 코드는 아래입니다.
// [TodoListItem.scss] 파일 생성
.TodoListItem {
padding: 1rem;
display: flex;
align-items: center;
&:nth-child(even) {
background: #f8f9fa;
}
.checkbox {
cursor: pointer;
flex: 1;
display: flex;
align-items: center;
svg {
font-size: 1.5rem;
}
.text {
margin-left: 0.5rem;
flex: 1;
}
&.checked {
svg {
color: #22b8cf;
}
.text {
color: #adb5bd;
text-decoration: line-through;
}
}
}
.remove {
display: flex;
align-items: center;
font-size: 1.5rem;
color: #ff6b6b;
cursor: pointer;
&:hover {
color: #ff8787;
}
}
& + & {
border-top: 1px solid #dee2e6;
}
}
먼저 js파일의 코드는 아래와 같습니다.
// [TodoList.js] 파일 생성
import React from ‘react‘;
import TodoListItem from ‘./TodoListItem‘;
import ‘./TodoList.scss‘;
const TodoList = () => {
return (
<div className=“TodoList“>
<TodoListItem />
<TodoListItem />
<TodoListItem />
</div>
);
};
export default TodoList;
다음으로 scss 코드는 아래와 같습니다.
// [TodoList.scss] 파일 생성
.TodoList {
min-height: 320px;
max-height: 513px;
overflow-y: auto;
}
지금까지 생성한 4개의 js 템플릿을 App.js에 불러와 렌더링 합니다.
// [App.js] 생성 파일
import React from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
return (
<TodoTemplate>
<TodoInsert />
<TodoList />
</TodoTemplate>
);
};
export default App;
생성한 UI를 토대로, 여러 가지 기능을 구현해보고자 합니다.
이는 App.js 파일에 소스 코드를 추가힙으로서 설정할 수 있습니다.
다양한 기능을 구현하는 방법을 순차적으로 살펴봅시다.
아래는 일정 항목에 대한 상태를 조정하는 소스코드로, 'use state'를 사용해 'todos' 상태를 정의하고, 이를 'TodoList'의 props로 전달하는 내용입니다.
// [App.js] 파일 수정
import React, { useState } from ‘react‘; // 새로 추가한 내용
import TodoTemplate from ‘./components/TodoTemplate‘;
import TodoInsert from ‘./components/TodoInsert‘;
import TodoList from ‘./components/TodoList‘;
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;
또한 [TodoList.js]의 수정이 필요합니다.
TodoList의 props에서 각 항목의 고유 id, 내용, 완료여부 등의 값을 전달받아, 다시 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;
새로 받아 온 'todo' 값에 따라 수정된 UI를 노출시키기 위해서, [TodoListItem.js] 파일을 아래의 코드와 같이 수정합니다.
// [TodoListItem.js] 파일 수정
import React from ‘react‘;
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} 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;
일정 항목을 추가하는 기능을 구현하기 위해서는 [TodoInsert.js]와 [App.js]에서의 수정이 필요합니다.
먼저 'useState'를 사용해서 value 상태를 정의해 TodoInsert 컴포넌트에서 인풋의 입력 값을 관리할 수 있도록 만듭니다. 그리고 'onChange'함수를 생성해 인풋에 넣어주고, 자동으로 재사용할 수 있도록 'useCallback'을 이용합니다.
// [TodoInsert.js] 파일 수정
import React, { useState, useCallback } from ‘react‘;
import { MdAdd } from ‘react-icons/md‘;
import ‘./TodoInsert.scss‘;
const TodoInsert = () => {
const [value, setValue] = useState(“); // 새로 수정한 부분
const onChange = useCallback(e => { // 새로 수정한 부분
// conslog.log를 통해 입력 값이 잘 전달되는지 확인 가능
setValue(e.target.value); // 새로 수정한 부분
}, []); // 새로 수정한 부분
return (
<form className=“TodoInsert“>
<input
placeholder=“할 일을 입력하세요“
value={value} // 새로 수정한 부분
onChange={onChange} // 새로 수정한 부분
/>
<button type=“submit“>
<MdAdd />
</button>
</form>
);
};
export default TodoInsert;
인풋에 입력한 텍스트가 잘 전달되는지 확인하는 방법 : * value와 onChange 함수 설정 - coonsol.log 를 통해 확인하기
* 리액트 개발자 도구 사용하기 (https://chrome.google.com/webstore/category/extensions) |
다음으로, [App.js] 파일을 수정해 todos 배열에 새 객체를 추가합니다.
바로 'onInsert'함수를 이용하는 것인데요, 새로운 객체를 만들 때마다 id 값에 1씩 더해준다는 특징이 있습니다.
또한 이 함수를 TodoInsert의 컴포넌트인 props로 설정합니다.
// [App.js] 파일 수정
import React, { useState, useRef, useCallback } from ‘react‘; //새로 수정
import TodoTemplate from ‘./components/TodoTemplate‘;
import TodoInsert from ‘./components/TodoInsert‘;
import TodoList from ‘./components/TodoList‘;
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: ‘리액트의 기초 알아보기‘,
checked: true,
},
{
id: 2,
text: ‘컴포넌트 스타일링해 보기‘,
checked: true,
},
{
id: 3,
text: ‘일정 관리 앱 만들어 보기‘,
checked: false,
},
]);
const nextId = useRef(4); //새로 수정
const onInsert = useCallback( //새로 수정
text => {
const todo = {
id: nextId.current,
text,
checked: false,
};
setTodos(todos.concat(todo));
nextId.current += 1; // nextId 1씩 더하기
},
[todos], //새로 수정
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} /> //새로 수정
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default App;
그리고 [TodoInsert.js] 파일을 한번 더 수정해줄 텐데, onSubmit 이벤트를 설정해 버튼을 클릭하면 onInsert 함수에 현재 value 값을 파라미터로 넣어서 호출하며 현재 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 값 초기화
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 이벤트와 onClick 이벤트의 차이점 :
* onSubmit 이벤트 : 인풋에서 Enter 키를 눌러도 이벤트 발생
* onClick 이벤트 : 인풋에서 Enter 키를 누르면 이벤트 발생하지 않음 |
배열 내장함수인 filter를 사용하는 리액트 컴포넌트에서 배열의 불변성을 지킬 수 있고, 동시에 원소를 제거할 수 있습니다. 여기에는 조건을 확인해주는 함수를 파라미터로 넣어주는데, true 값을 반환하면 새로운 배열에 포함시킬 수 있습니다.
[App.js] 파일에서 이를 사용해 같은 항목의 id를 가질 경우, todos 배열에서 삭제하는 함수를 생성할 수 있습니다.
마지막에 이를 TodoList의 props로 설정하는 것도 잊지 마세요!
새로 추가 및 수정한 부분은 아래의 코드와 같습니다.
// [App.js] 파일 수정 및 추가사항
const onRemove = useCallback(
id => {
setTodos(todos.filter(todo => todo.id != = id));
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} />
</TodoTemplate>
);
};
또한 [TodoList.js] 파일에도 수정할 점이 있는데요, 바로 방금 만든 'onRemove' 함수를 'TodoListItem'에 전달한다는 부분입니다.
해당 코드는 아래와 같습니다.
// [TodoList.js] 수정 및 추가 사항
const TodoList = ({ todos, onRemove }) => {
return (
<div className=“TodoList“>
{todos.map(todo => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</div>
);
};
위의 코드에 의해서 'TodoListItem'는 전달받은 내용을 현재 자신의 id 값을 넣어 적용하는 코드는 아래와 같습니다.
// [TodoListItem.js] 수정 코드
import React from 'react';
import {
MdCheckBoxOutlineBlank,
MdCheckBox,
MdRemoveCircleOutline,
} 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;
조금 전에 만든 삭제 기능과 유사하되, onRemove 함수 대신 onToggle이라는 함수를 생성 및 전달하면 됩니다.
먼저 [App.js]에 onToggle을 map을 이용해 구현합니다.
// [App.js] 파일 수정
const onToggle = useCallback(
id => {
setTodos(
todos.map(todo => // 특정 id를 가진 객체의 checked 값을 반전
todo.id === id ? { ...todo, checked: !todo.checked } : todo,
),
);
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} /> // 삼항 연산자. id가 다른 경우만 변화 줌.
</TodoTemplate>
);
};
그리고 TodoListItem를 수정하면 아래와 같습니다.
// [TodoListItem.js] 파일 수정
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>
);
};
이어서 TodoList를 수정해 봅시다.
// [TodoList.js] 파일 수정
const TodoList = ({ todos, onRemove, onToggle }) => {
return (
<div className=“TodoList“>
{todos.map(todo => (
<TodoListItem
todo={todo}
key={todo.id}
onRemove={onRemove}
onToggle={onToggle}
/>
))}
</div>
);
};
이상으로 간단한 프로젝트 생성을 끝마쳤습니다.
다만, 이것보다 커다란 프로젝트의 경우 이 외에도, 컴포넌트 리렌더링 최적화 과정 등이 필요할 수 있습니다.
이에 대한 자세한 내용은 11장에서 다룰 수 있습니다.
Corner React1
Editor: 라마
[리액트 스타터1] 12장. immer를 사용하여 더 쉽게 불변성 유지하기 (1) | 2022.12.29 |
---|---|
[리액트 스타터1] 11장. 컴포넌트 성능 최적화 (0) | 2022.12.29 |
[리액트 스타터1] 9장. 컴포넌트 스타일링 (0) | 2022.12.01 |
[리엑트 스타터1] 8장. Hooks (0) | 2022.11.24 |
[리엑트 스타터1] 7장. 컴포넌트의 라이프 사이클 메서드 (0) | 2022.11.17 |