1. 새로운 폴더 ‘project2’를 만든다.
2. 터미널을 열고 npx create-react-app . 명령을 입력해 리액트 앱을 생성한다.
3. 다음 4개의 불필요한 파일은 삭제한다.
import "./App.css";
2
3function App() {
4 return <div className="App"></div>;
5}
6export default App;
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
터미널에서 npm run start를 입력해 리액트 앱을 시작한다.
좌우 여백이 넓으며 페이지의 정중앙에 자리에 위치하게 한다.
import "./App.css";
function App() {
return (
<div className="App">
<h2>헬로 리액트</h2>
</div>
);
}
export default App;
body {
margin: 0px;
}
.App {
max-width: 500px;
width: 100%;
margin: 0 auto;
box-sizing: border-box;
padding: 20px;
border: 1px solid gray;
}
App에는 3개의 자식 컴포넌트 Header, TodoEditor, TodoList를 각각 세로로 배치하게 한다.
import "./App.css";
function App() {
return (
<div className="App">
<div>Header</div>
<div>Todo Editor</div>
<div>Todo List</div>
</div>
);
}
export default App;
.App {
(...)
display: flex;
flex-direction: column;
gap: 30px;
}
페이지 최상단에 위치할 Header 컴포넌트를 만든다.
1. 우선 src에 component 폴더를 만든다.
2. Header.js를 생성한다.
Header 컴포넌트가 오늘의 날짜를 렌더링한다.
const Header = () => {
return (
<div className="Header">
<h3>오늘은 📅</h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
export default Header;
Header 컴포넌트를 페이지에 렌더링하려면 App의 자식으로 배치한다.
import "./App.css";
import Header from "./component/Header";
function App() {
return (
<div className="App">
<Header />
<div>Todo Editor</div>
<div>Todo List</div>
</div>
);
}
export default App;
.Header h1 {
margin-bottom: 0px;
color: #1f93ff;
}
Header.css를 Header.js에서 불러온다.
import "./Header.css";
component 폴더에 컴포넌트와 스타일을 정의할 TodoEditor.js와 TodoEditor.css를 각각 생성한다.
import "./TodoEditor.css";
const TodoEditor = () => {
return <div className="TodoEditor">TodoEditor Component</div>;
};
export default TodoEditor;
import "./App.css";
import Header from "./component/Header";
import TodoEditor from "./component/TodoEditor";
function App() {
return (
<div className="App">
<Header />
<TodoEditor />
<div>Todo List</div>
</div>
);
}
export default App;
TodoEditor의 UI를 만든다.
import "./TodoEditor.css";
const TodoEditor = () => {
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏️ </h4>
<div className="editor_wrapper">
<input placeholder="새로운 Todo..." />
<button>추가</button>
</div>
</div>
);
};
export default TodoEditor;
.TodoEditor .editor_wrapper {
width: 100%;
display: flex;
gap: 10px;
}
.TodoEditor input {
flex: 1;
box-sizing: border-box;
border: 1px solid rgb(220, 220, 220);
border-radius: 5px;
padding: 15px;
}
.TodoEditor input:focus {
outline: none;
border: 1px solid #1f93ff;
}
.TodoEditor button {
cursor: pointer;
width: 80px;
border: none;
background-color: #1f93ff;
color: white;
border-radius: 5px;
}
1. TodoList 컴포넌트 만들기
컴포넌트와 스타일을 정의하는 TodoList.js와 TodoList.css를 각각 생성한다.
import "./TodoList.css";
const TodoList = () => {
return <div className="TodoList">TodoList Component</div>;
};
export default TodoList;
TodoList를 App 컴포넌트의 자식으로 배치한다.
import "./App.css";
import Header from "./component/Header";
import TodoEditor from "./component/TodoEditor";
import TodoList from "./component/TodoList";
function App() {
return (
<div className="App">
<Header />
<TodoEditor />
<TodoList />
</div>
);
}
export default App;
TodoList 컴포넌트는 크게 할 일 아이템을 조회하는 검색 폼과 조회한 할 일 아이템을 목록 형태로 보여주는 리스트 두 부분으로 구성한다.
TodoList 컴포넌트 상단에 위치할 검색 폼부터 만들자.
import "./TodoList.css";
const TodoList = () => {
return (
<div className="TodoList">
<h4>Todo List 🌱</h4>
<input className="searchbar" placeholder="검색어를 입력하세요" />
</div>
);
};
export default TodoList;
/* 검색 폼에 스타일 적용 */
.TodoList .searchbar {
margin-bottom: 20px;
width: 100%;
border: none;
border-bottom: 1px solid rgb(220, 220, 220);
box-sizing: border-box;
padding-top: 15px;
padding-bottom: 15px;
}
/* 검색 폼을 클릭했을 때의 스타일 적용 */
.TodoList .searchbar:focus {
outline: none;
border-bottom: 1px solid #1f93ff;
}
검색 폼에서 검색어를 입력하면 조건에 일치하는 할 일 아이템이 하단에 리스트로 출력된다. 아직 TodoItem 컴포넌트를 만들지 않았으므로 할 일 아이템을 출력하지는 못한다.
2. TodoItem 컴포넌트 만들기
component 폴더에서 컴포넌트와 스타일을 정의하는 TodoItem.js와 TodoItem.css를 각각 생성한다.
import "./TodoItem.css";
const TodoItem = () => {
return (
<div className="TodoItem">
<div className="checkbox_col">
<input type="checkbox" />
</div>
<div className="title_col">할 일</div>
<div className="date_col">{new Date().toLocaleDateString()}</div>
<div className="btn_col">
<button>삭제</button>
</div>
</div>
);
};
export default TodoItem;
3. TodoList에 TodoItem 컴포넌트 배치하기
import TodoItem from "./TodoItem";
import "./TodoList.css";
const TodoList = () => {
return (
<div className="TodoList">
<h4>Todo List 🌱</h4>
<input className="searchbar" placeholder="검색어를 입력하세요" />
<div className="list_wrapper">
<TodoItem />
<TodoItem />
<TodoItem />
</div>
</div>
);
};
export default TodoList;
(...)
TodoList .list_wrapper {
display: flex;
flex-direction: column;
gap: 20px;
}
/* 할 일 아이템 박스 스타일 적용 */
.TodoItem {
display: flex;
align-items: center;
gap: 20px;
padding-bottom: 20px;
border-bottom: 1px solid rgb(240, 240, 240);
}
/* 체크박스를 감싼 박스에 스타일 적용 */
.TodoItem .checkbox_col {
width: 20px;
}
/* 할 일 텍스트를 감싼 박스에 스타일 적용 */
.TodoItem .title_col {
flex: 1;
}
/* 할 일 아이템 등록 시간을 감싼 박스에 스타일 적용 */
.TodoItem .date_col {
font-size: 14px;
color: gray;
}
/* 삭제 버튼에 스타일 적용 */
.TodoItem .btn_col button {
cursor: pointer;
color: gray;
font-size: 14px;
border: none;
border-radius: 5px;
padding: 5px;
}
.App {
max-width: 500px;
width: 100%;
margin: 0 auto;
box-sizing: border-box;
padding: 20px;
/* border: 1px solid gray; <- 삭제하거나 주석 처리 하세요 */
display: flex;
flex-direction: column;
gap: 30px;
}
아이템부터 생성하자.
import { useState } from "react";
(..)
function App() {
const [todo, setTodo] = useState([]);
return (
(...)
);
}
export default App;
이렇게 현실의 사물이나 개념을 프로그래밍 언어의 객체와 같은 자료구조로 표현하는 행위를 ‘데이터 모델링’이라 한다.
하나의 할 일 아이템에는 일의 완료 여부, 일의 종류, 생성 날짜 등 3가지 정보가 담겨 있다. 세 요소는 각각 isDone, content, createdDate라는 별도의 이름으로 구분한다.
목(Mock) 데이터란 모조품 데이터라는 뜻이다. 기능을 완벽히 구현하지 않은 상태에서 테스트를 목적으로 사용하는 데이터이다.
(...)
const mockTodo = [
{
id: 0,
isDone: false,
content: "React 공부하기",
createdDate: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "빨래 널기",
createdDate: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "노래 연습하기",
createdDate: new Date().getTime(),
},
];b
function App() {
const [todo, setTodo] = useState(mockTodo);
b
return (
(...)
);
}
export default App;
CRUD의 첫 번째 기능인 Create를 구현하자.
TodoEditor 컴포넌트에서 <추가> 버튼을 클릭하면 App에 사용자가 입력한 할 일 데이터를 전달하고 추가 이벤트가 발생했음을 알려야 한다.
(...)
function App() {
const [todo, setTodo] = useState(mockTodo);
const onCreate = (content) => {
const newItem = {
id: 0,
content,
isDone: false,
createdDate: new Date().getTime(),
};
setTodo([newItem, ...todo]);
};
return (
(...)
);
}
export default App;
import { useState, useRef } from "react";
(...)
function App() {
const idRef = useRef(3);
(...)
}
export default App;
(...)
function App() {
(...)
const idRef = useRef(3);
const onCreate = (content) => {
const newItem = {
id: idRef.current,
content,
isDone: false,
createdDate: new Date().getTime(),
};
setTodo([newItem, ...todo]);
idRef.current += 1;
};
return (
(...)
);
}
export default App;
(...)
function App() {
(...)
return (
<div className="App">
<Header />
<TodoEditor onCreate={onCreate} />
<TodoList />
</div>
);
}
export default App;
import "./TodoEditor.css";
const TodoEditor = ({ onCreate }) => {
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 🔏 </h4>
<div className="editor_wrapper">
<input placeholder="새로운 Todo..." />
<button>추가</button>
</div>
</div>
);
};
export default TodoEditor;
다음으로 TodoEditor 컴포넌트의 할 일 입력 폼에서 사용자가 입력하는 새 할 일 데이터를 저장할 State를 만든다.
import { useState } from "react";
import "./TodoEditor.css";
const TodoEditor = ({ onCreate }) => {
const [content, setContent] = useState("");
const onChangeContent = (e) => {
setContent(e.target.value);
};
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏ </h4>
<div className="editor_wrapper">
<input ③
value={content}
onChange={onChangeContent}
placeholder="새로운 Todo..."
/>
<button>추가</button>
</div>
</div>
);
};
export default TodoEditor;
[Components] 탭에서 App의 TodoEditor를 클릭해 State(content)에 사용자가 지금 입력한 내용이 제대로 반영되는지 확인한다.
다음으로 <추가> 버튼을 클릭하면, 함수 onCreate를 호출하는 버튼 클릭 이벤트 핸들러를 만든다.
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const onSubmit = () => {
onCreate(content);
};
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏ </h4>
<div className="editor_wrapper">
<input
value={content}
onChange={onChangeContent}
placeholder="새로운 Todo..."
/>
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
};
export default TodoEditor;
1. 빈 입력 방지하기
할 일 입력 폼을 관리할 Ref 객체를 하나 만들고, 함수 onSubmit에서 content 값이 비어 있으면 입력 폼에 포커스를 구현하는 방식이다.
import { useState, useRef } from "react";
import "./TodoEditor.css";
const TodoEditor = ({ onCreate }) => {
const [content, setContent] = useState("");
const inputRef = useRef();
(...)
const onSubmit = () => {
if (!content) {
inputRef.current.focus();
return;
}
onCreate(content);
};
return (
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏ </h4>
<div className="editor_wrapper">
<input
ref={inputRef}
value={content}
onChange={onChangeContent}
placeholder="새로운 Todo..."
/>
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
};
export default TodoEditor;
2. 아이템 추가 후 입력 폼 초기화하기
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const onSubmit = () => {
if (!content) {
inputRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
(...)
};
export default TodoEditor;
3. <Enter> 키를 눌러 아이템 추가하기
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const onKeyDown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
return(
<div className="TodoEditor">
<h4>새로운 Todo 작성하기 ✏ </h4>
<div className="editor_wrapper">
<input
ref={inputRef}
value={content}
onChange={onChangeContent}
onKeyDown={onKeyDown}
placeholder="새로운 Todo..."
/>
<button onClick={onSubmit}>추가</button>
</div>
</div>
)
};
export default TodoEditor;
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)
Editor: yunseul
1(2,3,4,5). 데이터를 다루는 4개의 기능, 즉 ( ), ( ), ( ), ( ) 기능을 앞글자만 따서 ( )라고 한다.
6. 현실의 사물이나 개념을 프로그래밍 언어의 객체와 같은 자료구조로 표현하는 행위를 ( )이라 한다.
7. ( ) 데이터란 모조품 데이터라는 뜻이다.
8. ( )를 사용하면 아이템을 추가할 때마다 중복 id가 만들어져 문제를 해결할 수 있다.
1. 위에 프로젝트에서 App.js에 새로운 Ref 객체를 생성하도록 코드를 다시 작성해 보자. 초깃값이 3인 Ref 객체를 생성해 idRef에 저장하도록 해보자.
2. 아이템 추가 후 입력 폼 초기화하는 코드를 다시 작성해 보자.
추가(Create), 조회(Read), 수정(Update), 삭제(Delete), CRUD / 데이터 모델링 / 목(Mock) / Ref 객체
import { useState, useRef } from "react";
(...)
function App() {
const idRef = useRef(3);
(...)
}
export default App;
(...)
const TodoEditor = ({ onCreate }) => {
(...)
const onSubmit = () => {
if (!content) {
inputRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
(...)
};
export default TodoEditor;
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), p2. 할 일 관리 앱 만들기 0~4장.
Editor: yunseul
[리액터 스타터2] 7장. useReducer와 상태 관리 / 8장. 최적화 (2) | 2023.12.29 |
---|---|
[리액터 스타터2] project 2 [할 일 관리] 앱 만들기 2 (0) | 2023.12.23 |
[리액터 스타터2] Hooks (1) | 2023.11.24 |
[리액트 스타터2] project 1 [카운터] 앱 만들기 ~ 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2023.11.17 |
[리액트 스타터2] 5장. 리액트의 기본 기능 다루기(2) (0) | 2023.11.10 |