생성형 AI 활용한 클라우드&보안 전문가 양성 캠프
TODO 일정 관리 앱 만들어보기 feat 리액트
클론 코딩느낌으로 하나씩 따라 가면서 만들어 보겠습니다.
우선 프로젝트를 생성합니다.
명령 프롬포트를 켜서 경로를 잡아 줍니다.
저는 c:\javaScript 디렉토리에 todo-app 생성 하고자 합니다.
생성 할때의 명령어는 npx create-react-app todo-app
c:\javaScript> npx create-react-app todo-app
이렇게 작성한 후 설치를 기다립니다!!!!
설치가 완료되면 "Happy Hacking" 이라는 문구가 나옵니다.
이제 CRA(create-react-app)으로 생성한 파일을 프로젝트에 맞도록 초기화 하겠습니다.
우선 index.css 을 수정해줍니다.
index.css
body {
margin: 0;
padding: 0;
background: #e9ecef;
}
초기화 내용으로는 모든 엘리먼트의 바깥 여백을 없애고 모든 엘리먼트의 안쪽 여백도 없애고 연한 회색으로 페이지
배경색을 설정합니다.
이제 app.js를 수정 해봅시다.
App.js
import './App.css';
function App() {
return (
<div>TODO App</div>
);
}
export default App;
컴포넌트 디렉토리 만들어주고 기능을 하나씩 구현해봅니다.
디렉토리 이름은 components 로 만들어 주고 js 파일을 만들어 줍니다.
TodoTemplate.js
import "./TodoTemplate.css";
const TodoTemplate = (props) => {
return (
<>
<div className="container">
<div className="title">일정관리</div>
<div className="content">{props.children}</div>
</div>
</>
);
};
export default TodoTemplate;
부모 컴포넌트에서 전달한 내용을 props.children을 통해 여기에 렌더링 한다
이러면 재사용 가능한 컴포넌트가 된다.
이제 TodoInsert 컴포넌트 관련 파일을 생성해보자
Material Design Icons
https://react-icons.github.io/react-icons/icons/md/
우선 icon을 사용하기 위해 명령 프롬포트에 패키지를 설치한다.
c:\javascript\todo-app> npm install react-icons
이제 입력하는 js를 만들어주자
TodoInsert.js
import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";
export default function TodoInsert(props) {
return (
<>
<form className="insert">
<input type="text" placeholder="할일을 입력하세요."></input>
<button type="submit"><MdAdd/></button>
</form>
</>
);
};
react-icons 라이브러리에서 MdAdd(+)를 불러서 사용했다.
TodoListItem 컴포넌트 관련 파일을 생성합니다.
TodoListItem.js
import { MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";
export default function TodoListItem() {
return (
<>
<div className="item">
<div className="checkbox">
<MdCheckBoxOutlineBlank />
<div className="text">할 일</div>
</div>
<div className="remove">
<MdOutlineRemoveCircleOutline />
</div>
</div>
</>
);
};
TodoList 컴포넌트 관련 파일을 생성합니다.
TodoList.js
import TodoListItem from "./TodoListItem";
import "./TodoList.css";
export default function TodoList() {
return (
<>
<div className="list">
<TodoListItem />
<TodoListItem />
<TodoListItem />
</div>
</>
);
};
할 일 목록 기능 구현
App 컴포넌트에 할 일 목록 데이터를 관리하는 상태변수를 정의하고, TodoList 컴포넌트에 props 변수로 전달
App.js
import { useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
function App() {
const [todos, setTodos] = useState([
{ id: 1, checked: true, text: '자바스크립트 공부하기' },
{ id: 2, checked: false, text: '리액트 공부하기' },
{ id: 3, checked: false, text: '할 일 목록 앱 만들기' },
]);
return (
<div>
<TodoTemplate>
<TodoInsert />
<TodoList todos={todos} />
</TodoTemplate>
</div>
);
}
export default App;
TodoList 컴포넌트에서 props 변수로 전달된 배열 값을 이용해서 TodoListItem 컴포넌트를 추가
TodoList.js
import TodoListItem from "./TodoListItem";
import "./TodoList.css";
export default function TodoList({todos}) {
return (
<>
<div className="list">
{ todos.map(todo => <TodoListItem key={todo.id} todo={todo} />) }
</div>
</>
);
};
TodoListItem 컴포넌트에서 props 변수로 전달된 할 일 내용을 출력
TodoListItem.js
import { MdCheckBox, MdCheckBoxOutLineBlank, MdOutlineRemoveCircleOutLine } from "react-icons/md";
import "./TodoListItem.css";
export default function TodoListItem({todo}) {
console.log(todo);
//객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
const {id, checked, text} = todo;
return (
<>
<div className="item">
<div className={checked ? "checkbox checked" : "checkbox"}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className ="remove">
<MdOutlineRemoveCircleOutline />
</div>
</div>
</>
);
};
할 일 추가 기능 구현
TodoInsert 컴포넌트에 입력 기능을 구현
import { MdAdd } from "react-icons/md"
import "./TodoInsert.css";
import { useState } from "react";
export default function TodoInsert(props){
const [todo, setTodo] = useState('');
const changeTodo = e => setTodo(e.target.value);
return (
<>
<from className ="insert">
<input type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
<button type ="submit"><MdAdd/></button>
</from>
</>
);
};
App 컴포넌트에 todos 상태변수에 값을 추가하는 함수를 정의하고, 해당 함수를 TodoInsert 컴포넌트의 props 변수로 전달 => 할 일이 추가될 때 사용할 id값을 관리하는 변수를 정의 <= useRef를 이용해서 생성
App.js
import { useRef, useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
function App() {
const [todos, setTodos] = useState([
{ id: 1, checked: true, text: '자바스크립트 공부하기' },
{ id: 2, checked: false, text: '리액트 공부하기' },
{ id: 3, checked: false, text: '할 일 목록 앱 만들기' },
]);
const nextId = useRef(4);
const insertTodo = text => {
const newTodos = todos.concat({ id: nextId.current, checked: false, text });
setTodos(newTodos);
nextId.current ++;
};
return (
<div>
<TodoTemplate>
<TodoInsert />
<TodoList todos={todos} />
</TodoTemplate>
</div>
);
}
export default App;
TodoInsert 컴포넌트에 추가 버튼을 클릭했을 때 동작을 추가
TodoInsert.js
import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";
import { useState, useRef } from "react";
export default function TodoInsert({insertTodo}) {
const [todo, setTodo] = useState('');
const changeTodo = e => setTodo(e.target.value);
// 할 일 입력창에 포커스 부여에 사용할 ref 변수를 생성
const refInput = useRef();
const handleSubmit = e => {
// submit 이벤트가 발생했을 때 브라우저의 기본 동작이 발생하지 않도록 처리
e.preventDefault();
insertTodo(todo);
setTodo('');
refInput.current.focus();
};
return (
<>
<form className="insert" onSubmit={handleSubmit}>
<input ref={refInput} type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
<button type="submit"><MdAdd/></button>
</form>
</>
);
};
할 일 삭제 기능 구현
App 컴포넌트에 todos 상태변수의 값을 삭제하는 함수를 정의하고, 해당 함수를 TodoList 컴포넌트에 props 변수로 전달
App.js
import { useRef, useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
function App() {
const [todos, setTodos] = useState([
{ id: 1, checked: true, text: '자바스크립트 공부하기' },
{ id: 2, checked: false, text: '리액트 공부하기' },
{ id: 3, checked: false, text: '할 일 목록 앱 만들기' },
]);
const nextId = useRef(4);
const insertTodo = text => {
const newTodos = todos.concat({ id: nextId.current, checked: false, text });
setTodos(newTodos);
nextId.current ++;
};
const removeTodo = id => {
const newTodos = todos.filter(todo => todo.id !== id);
setTodos(newTodos);
};
return (
<div>
<TodoTemplate>
<TodoInsert insertTodo={insertTodo} />
<TodoList todos={todos} removeTodo={removeTodo} />
</TodoTemplate>
</div>
);
}
export default App;
TodoList 컴포넌트에서 props 변수로 전달받은 removeTodo 함수를 TodoListItem 컴포넌트의 props 변수로 전달
TodoList.js
import TodoListItem from "./TodoListItem";
import "./TodoList.css";
export default function TodoList({todos, removeTodo}) {
return (
<>
<div className="list">
{ todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} />) }
</div>
</>
);
};
TodoListItem 컴포넌트에서 삭제 버튼을 클릭하면 props 변수로 전달받은 removeTodo 함수를 호출
TodoListItem.js
import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";
export default function TodoListItem({todo, removeTodo}) {
console.log(todo);
// 객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
const {id, checked, text} = todo;
return (
<>
<div className="item">
<div className={checked ? "checkbox checked" : "checkbox"}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => removeTodo(id)}>
<MdOutlineRemoveCircleOutline />
</div>
</div>
</>
);
};
할 일 수정 기능 구현 => 할 일 체크 상태를 변경
App 컴포넌트에서 todos 상태변수에 값을 수정(cheked 속성의 값을 토글)하는 함수를 정의하고, 해당 함수를 TodoList
컴포넌트의 props 변수로 전달
App.js
import { useRef, useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
function App() {
const [todos, setTodos] = useState([
{ id: 1, checked: true, text: '자바스크립트 공부하기' },
{ id: 2, checked: false, text: '리액트 공부하기' },
{ id: 3, checked: false, text: '할 일 목록 앱 만들기' },
]);
const nextId = useRef(4);
const insertTodo = text => {
const newTodos = todos.concat({ id: nextId.current, checked: false, text });
setTodos(newTodos);
nextId.current ++;
};
const removeTodo = id => {
const newTodos = todos.filter(todo => todo.id !== id);
setTodos(newTodos);
};
const toggleCheck = id => {
const newTodos = todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo)
setTodos(newTodos);
};
return (
<div>
<TodoTemplate>
<TodoInsert insertTodo={insertTodo} />
<TodoList todos={todos} removeTodo={removeTodo} toggleCheck={toggleCheck} />
</TodoTemplate>
</div>
);
}
export default App;
TodoList 컴포넌트에서 props 변수로 전달받은 toggleCheck 함수를 TodoListItem 컴포넌트의 props 변수로 전달
TodoList.js
import TodoListItem from "./TodoListItem";
import "./TodoList.css";
export default function TodoList({todos, removeTodo, toggleCheck}) {
return (
<>
<div className="list">
{ todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} toggleCheck={toggleCheck} />) }
</div>
</>
);
};
TodoListItem 컴포넌트에서 체크박스를 클릭하면 props 변수로 전달받은 togglecheck 함수를 호출
TodoListItem.js
import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";
export default function TodoListItem({todo, removeTodo, toggleCheck}) {
console.log(todo);
// 객체 비구조화를 통해 todo 객체의 속성값을 지역변수의 값으로 할당
const {id, checked, text} = todo;
return (
<>
<div className="item">
<div className={checked ? "checkbox checked" : "checkbox"} onClick={() => toggleCheck(id)}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => removeTodo(id)}>
<MdOutlineRemoveCircleOutline />
</div>
</div>
</>
);
};
Context API와 useContext 훅을 이용해서 insertTodo, removeTodo,toggleCheck 함수를 props 변수로 전달하지 않고 사용할 수 있도록 수정
TodoContext.js
import { createContext } from "react";
const TodoContext = createContext();
export default TodoContext;
TodoProvider.js
import { useRef, useState } from "react";
import TodoContext from "./TodoContext";
export default function TodoProvider(props) {
const [todos, setTodos] = useState([
{ id: 1, checked: true, text: '자바스크립트 공부하기' },
{ id: 2, checked: false, text: '리액트 공부하기' },
{ id: 3, checked: false, text: '할 일 목록 앱 만들기' },
]);
const nextId = useRef(4);
const insertTodo = text => {
const newTodos = todos.concat({ id: nextId.current, checked: false, text });
setTodos(newTodos);
nextId.current++;
};
const removeTodo = id => {
const newTodos = todos.filter(todo => todo.id !== id);
setTodos(newTodos);
};
const toggleCheck = id => {
const newTodos = todos.map(todo => todo.id === id ? { ...todo, checked: !todo.checked } : todo)
setTodos(newTodos);
};
return (
<TodoContext.Provider value={{ todos, insertTodo, removeTodo, toggleCheck }}>
{props.children}
</TodoContext.Provider>
);
};
app,js => TodoProvicer 컴포넌트로 감싸고, 컨텍스트를 통해서 제공받을 수 있는 상태변수, 함수, props 변수를 삭제
APP.js
import { useRef, useState } from 'react';
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
import TodoTemplate from './components/TodoTemplate';
import TodoProvider from './components/TodoProvider';
function App() {
return (
<div>
<TodoProvider>
<TodoTemplate>
<TodoInsert />
<TodoList />
</TodoTemplate>
</TodoProvider>
</div>
);
}
export default App;
TodoInsert.js
import { MdAdd } from "react-icons/md";
import "./TodoInsert.css";
import { useState, useRef, useContext } from "react";
import TodoContext from "./TodoContext";
export default function TodoInsert() {
const {insertTodo} = useContext(TodoContext);
const [todo, setTodo] = useState('');
const changeTodo = e => setTodo(e.target.value);
// 할 일 입력창에 포커스 부여에 사용할 ref 변수를 생성
const refInput = useRef();
const handleSubmit = e => {
// submit 이벤트가 발생했을 때 브라우저의 기본 동작이 발생하지 않도록 처리
e.preventDefault();
insertTodo(todo);
setTodo('');
refInput.current.focus();
};
return (
<>
<form className="insert" onSubmit={handleSubmit}>
<input ref={refInput} type="text" placeholder="할일을 입력하세요." value={todo} onChange={changeTodo}></input>
<button type="submit"><MdAdd/></button>
</form>
</>
);
};
TodoList.js
import TodoListItem from "./TodoListItem";
import "./TodoList.css";
import { useContext } from "react";
import TodoContext from "./TodoContext";
export default function TodoList() {
const {todos} = useContext(TodoContext);
return (
<>
<div className="list">
{
// todos.map(todo => <TodoListItem key={todo.id} todo={todo} removeTodo={removeTodo} toggleCheck={toggleCheck} />)
todos.map(todo => <TodoListItem key={todo.id} todo={todo} />)
}
</div>
</>
);
};
TodoListItem.js
import { MdCheckBox, MdCheckBoxOutlineBlank, MdOutlineRemoveCircleOutline } from "react-icons/md";
import "./TodoListItem.css";
import { useContext } from "react";
import TodoContext from "./TodoContext";
export default function TodoListItem({todo}) {
const {removeTodo, toggleCheck} = useContext(TodoContext);
const {id, checked, text} = todo;
return (
<>
<div className="item">
<div className={checked ? "checkbox checked" : "checkbox"} onClick={() => toggleCheck(id)}>
{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
<div className="text">{text}</div>
</div>
<div className="remove" onClick={() => removeTodo(id)}>
<MdOutlineRemoveCircleOutline />
</div>
</div>
</>
);
};