0308
์ด์ ์ ๋ง๋ค์๋ to-do list ๋ CRA ํ๋ก์ ํธ๋ก ์์ฑํด JS ์ธ์ด๋ก ์จ์ ๋ง๋ ๊ฒ
์ด๋ฒ์๋ Vite - React - TypeScript ํ๋ก์ ํธ๋ก ์์ฑํด์ JS => TS ๋ก ๋ฐ๊ฟ๋ดค๋ค
(vite ์ ํตํด ๋ฐ๋ก typescript ํ ํ๋ฆฟ์ผ๋ก ์ค์ ํด ๋ง๋ค๋ฉด ์์์ tsconfig.json ๋ฑ ์ค์ ์ด ๋์ด์์ผ๋ ๊ทธ๋ฅ ํ์ผํ์ฅ์๋ง ts / tsx ๋ก ๋ณ๊ฒฝํด์ ์ฐ๋ฉด ๋๋ค ใ ใ
๋ค๋ง ํจํค์ง ์ค์น๋ ts ์์ ๊ณ ๋ คํด์ฃผ์
๋๋ yarn ํจํค์ง ๊ด๋ฆฌ์๋ฅผ ์ผ๋ค. (npm ์ด๋ฉด npm install ~ ) )
yarn add typescript @types/node @types/react @types/react-dom
yarn add @types/styled-components (์คํ์ผ์ปดํฌ๋ํธ๋ฅผ ์ฐ๋ฉด)
( yarn add --dev ๋ก devDependencies๋ก ์ค์น ํ ์ ์์ )
yarn add react-router-dom ๋ ํด์ค์ผ ํ๋ ๊ฑฐ ๊ฐ๋ค..?
๋ํ TS ์๋ฒ๋ฅผ ๊ป๋ค ํค๋ฉด ํจํค์ง ์ค์น ๋ฑ ์ ์ ์ฉ๋ ์๋ ์์ด์..
VSC์์
ctrl + shift + p ํน์ ๋งจ ์ ๊ฒ์์ฐฝ์ > ์น๊ณ
=> TypeScript: Restart TS Server
์ ํด์ฃผ๋ฉด ๋์๋ ์ ์๋ค. ์ด๋ฐ ์์ผ๋ก ์๋ฌ๊ฐ ์ฌ๋ผ์ง๊ธฐ๋ ํ์ !!
์์ง TS๊ฐ ์ต์์น ์์์ ์์ํ๊ณ ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค.
๋นจ๊ฐ ์ค ์๋ฌ๊ฐ ๋จ๋ ๊ฑธ ํด๊ฒฐํด๊ฐ๋ฉด์ ํด์ฃผ๋ฉด ๋๋๋ฐ ..
๋ง๋ฅ any ๊ฐ์ ์ํผํ์ ? ์ ์จ์ฃผ๋ ๊ฑด TS๋ฅผ ์ฐ๋ ์๋ฏธ๊ฐ ์๊ธฐ ๋๋ฌธ์, ํ์ ์ ์ ์ง์ ํด์ฃผ๋ ๊ฒ, ๋
possibly undefined ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ ๊ฒ์ด ํ์ํ๋ค.
๋ด ๊ฒฝ์ฐ optional chaining (?) ์ผ๋ก ํด๊ฒฐ์ด ๋๋ ๊ฒ ๋ง์์ ์์ฃผ ์ผ๋ค.
? : ํ์ .. ํน์ ?. ๋ฉ์๋.. ์ด๋ฐ์์ผ๋ก !
< props - drilling ๋ฐฉ์์์ TS๋ก ๋ฐ๊พธ๊ธฐ >
src ํด๋ - types ํด๋์ todoType.ts ๋ฅผ ๋ง๋ค๊ณ , ๋ค์๊ณผ ๊ฐ์ด ์์ฑํด์ ํ์ (interface)์ import ํด์์ ์ผ๋ค
export interface Todo {
id: string;
title: string;
content: string;
deadline: string;
isDone: boolean;
}
export interface TodoSetList {
todoList: Todo[];
setTodoList: (cb: (todoList: Todo[]) => Todo[]) => void;
}
export interface InProgressTodo extends Todo {
isDone: false;
}
export interface DoneTodo extends Todo {
isDone: true;
}
assets ํด๋์๋ sampleTodo.ts ๋ฅผ ๋ง๋ค์ด๋๊ณ
Router.tsx์์ => todoList useState๋ฅผ ๊ฐ๊ฐ Home, Detail ํ์ด์ง์ props ๋ก ์ ๋ฌ
const [todoList, setTodoList] = useState<Todo[]>([sampleTodo]);
// ์๋ต
<Route
path="/"
element={<Home todoList={todoList} setTodoList={setTodoList} />}
/>
๊ทธ๊ฑธ ๋ฐ์ Home.tsx์์๋ ๋๋ค์ => TodoController.tsx ๋ก props ์ ๋ฌ
const Home = ({ todoList, setTodoList }: TodoSetList) => {
return (
<div>
<Header />
<TodoController todoList={todoList} setTodoList={setTodoList} />
</div>
);
};
TodoController.tsx
์์๋ todoList, setTodoList ๋ฅผ props ๋ก ๋ฐ๊ณ (ํ์ ์ ์์ ๋ง๋ค์ด๋ TodoSetList ๋ฅผ import)
e ์ด๋ฒคํธ ๊ฐ์ฒด ๋ฑ์ ๋ํ ํ์ ๋ ํจ์ ์ ์ธ๋ถ์์ ์ ์ํด์ฃผ๊ธฐ
sortTodoItems ํจ์ ๋ด์์ new Date ... ํ๋ ๋ถ๋ถ๋ TS ๋ก ๋ฐ๊พธ๋ ์๋ฌ๊ฐ ๋ ์, ๊ตฌ๊ธ๋ง ํ .getTime()์ ์ ์ฉํ๋ ํด๊ฒฐ
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import CustomOrderSelect from "../common/CustomOrderSelect";
import TodoList from "./TodoList";
import {
DoneTodo,
InProgressTodo,
Todo,
TodoSetList,
} from "../../types/todoType";
import { ListsSection } from "../../styles/TodoStyle";
function TodoController({ todoList, setTodoList }: TodoSetList) {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [deadline, setDeadline] = useState("");
const [sortOrder, setSortOrder] = useState<string>("desc");
// input์ value๊ฐ ๊ฐ์ ธ์ค๊ธฐ
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
// ์๋ต ...
// ์ถ๊ฐ: ๋ง๊ฐ๋ ์ง ์ค๋ฆ์ฐจ์ ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ ๋๋กญ๋ค์ด ๋ฉ๋ด _select ์ค์ ์
const handleSortOrderChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSortOrder(e.target.value);
sortTodoItems(sortOrder);
};
// todoItem ์ ๋ ฌํ๋ ํจ์
const sortTodoItems = (sortOrder: string) => {
setTodoList((prevTodoList) =>
[...prevTodoList].sort((a, b) => {
if (sortOrder === "asc") {
return (
new Date(a.deadline).getTime() - new Date(b.deadline).getTime()
);
} else {
return (
new Date(b.deadline).getTime() - new Date(a.deadline).getTime()
);
}
})
); // ์ ๋ ฌ๋ todoitem์ผ๋ก todolist ์ํ ์
๋ฐ์ดํธ
};
// ์ถ๊ฐํ๊ธฐ ๋ฒํผ addTodoHandler
const addTodoHandler = (newTodo: Todo) => {
setTodoList((prevTodoList) => [...prevTodoList, newTodo]);
};
// formํ๊ทธ์ ๋ค์ด๊ฐ๋ ํจ์ - ์
๋ ฅ ํ ์ถ๊ฐํ๊ธฐ๋ก ์คํ
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const nextTodo: Todo = {
id: crypto.randomUUID(),
title,
content,
deadline,
isDone: false,
};
if (!title || !content) {
alert("์ ๋ชฉ๊ณผ ๋ด์ฉ ๋ชจ๋ ์
๋ ฅํด์ฃผ์ธ์");
// ์ด ๊ฒฝ์ฐ ์ด๊ธฐํ์์ด ์
๋ ฅ๋ด์ฉ ์ ์ง์ํด
return;
} else if (!deadline) {
addTodoHandler({
id: crypto.randomUUID(),
title,
content,
deadline: "9999-12-31",
isDone: false,
});
setTitle("");
setContent("");
setDeadline("");
} else {
addTodoHandler(nextTodo);
setTitle("");
setContent("");
setDeadline("");
}
};
// ์ญ์ ๋ฒํผ: filter๋ฉ์๋๋ก ํด๋นid์ ์นด๋๋นผ๊ธฐ
const deleteTodoHandler = (id: string) => {
setTodoList((prevTodoList) =>
prevTodoList.filter((todo) => todo.id !== id)
);
};
// Done ์๋ฃ&์๋ฃ์ทจ์ ๋ฒํผ (ํ ๊ธ)
const onToggleTodoItem = (id: string) => {
setTodoList((prevTodoList) =>
prevTodoList.map((todo) => {
if (todo.id === id) {
return { ...todo, isDone: !todo.isDone };
}
return todo;
})
);
};
const workingTodoList = todoList.filter(
(todo) => todo.isDone === false
) as InProgressTodo[];
const doneTodoList = todoList.filter(
(todo) => todo.isDone === true
) as DoneTodo[];
return (
<main>
<TodoForm
onSubmit={handleFormSubmit}
valueTitle={title}
valueContent={content}
valueDeadline={deadline}
onChangeTitle={handleTitleInputChange}
onChangeContent={handleContentInputChange}
onChangeDeadline={handleDeadlineInputChange}
/>
{/* ์์์ ๋ ฌ selectํ๊ทธ ์น์
*/}
<CustomOrderSelect
selectValue={sortOrder}
selectOnChange={handleSortOrderChange}
>
๋ง๊ฐ์ผ ์์ผ๋ก ๋ณด๊ธฐ
</CustomOrderSelect>
<ListsSection>
<TodoList
type="working"
todoList={workingTodoList}
deleteTodoHandler={deleteTodoHandler}
onToggleTodoItem={onToggleTodoItem}
>
Working ๐โ๏ธ
</TodoList>
<TodoList
type="done"
todoList={doneTodoList}
deleteTodoHandler={deleteTodoHandler}
onToggleTodoItem={onToggleTodoItem}
>
Done ๐
</TodoList>
</ListsSection>
</main>
);
}
export default TodoController;
์ฐธ๊ณ ๋ก ์ ์ฝ๋์์,
input value ๊ฐ์ title, content, deadline ์ ๋ฐ์์ ์ผ์ผ์ด onChange ๋ก e.target.value ๋ฅผ set State ํด์ฃผ๊ณ ์๋๋ฐ, =>
Redux Toolkit ๋ฐฉ์์ผ๋ก ๋ฆฌํฉํฐ๋งํ ๋์๋, ๊ทธ๋ฅ onSubmit ํจ์๋ก, e.currentTarget. 'input์ name' ์ ์จ์ ๋ฐ๋ก nextTodo ์ ๋ฃ๋ ๋ฐฉ์์ผ๋ก ๋ฐ๊ฟ์ฃผ์๋ค. (useState ์์ด)
// formํ๊ทธ์ ๋ค์ด๊ฐ๋ ํจ์ - ์
๋ ฅ ํ ์ถ๊ฐํ๊ธฐ๋ก ์คํ
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const title = formData.get("title") as string;
const content = formData.get("content") as string;
const deadline = formData.get("deadline") as string;
const nextTodo: Todo = {
id: crypto.randomUUID(),
title,
content,
deadline,
isDone: false,
};
if (!title || !content) {
alert("์ ๋ชฉ๊ณผ ๋ด์ฉ ๋ชจ๋ ์
๋ ฅํด์ฃผ์ธ์");
// ์ด ๊ฒฝ์ฐ ์ด๊ธฐํ์์ด ์
๋ ฅ๋ด์ฉ ์ ์ง์ํด
return;
} else if (!deadline) {
addTodoHandler({
id: crypto.randomUUID(),
title,
content,
deadline: "9999-12-31",
isDone: false,
});
e.currentTarget.reset();
} else {
addTodoHandler(nextTodo);
e.currentTarget.reset();
}
};
TodoForm.tsx
๋ค์๊ณผ ๊ฐ์ด props๋ฅผ ๋ฐ๊ณ ์๋ค .
๊ธฐ๋๊น type ์ ๋ฐ๋ก ๋นผ์ฃผ๋๊ฒ ์ข์์๋ ค๋ ..!?
props ์ ๋ฌ ์์ FC ๋ฅผ ์ด๋ค๊ฑฐ๋ .. PropsWithChildren ๋ children๋๊ฒจ์ค ๋ (์๋ ์ฝ๋๋ง๊ณ ) ์ฌ์ฉํด๋ณด๊ณ ์ถ์๋๋ฐ, ์ ๋์ง ์์์ ์ผ๋จ ์ด๋ ๊ฒ ์์ฑํด๋์๋ค. ใ ใ
function TodoForm({
onSubmit,
valueTitle,
valueContent,
valueDeadline,
onChangeTitle,
onChangeContent,
onChangeDeadline,
}: {
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
valueTitle: string;
valueContent: string;
valueDeadline: string;
onChangeTitle: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChangeContent: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChangeDeadline: (e: React.ChangeEvent<HTMLInputElement>) => void;
}) {