[Mini Project] Note-App
๐ฉ๐ป๐ป Github Repository https://github.com/sw2377/note-app
์์ ๊ธฐ๊ฐ : 2024. 01. 09 ~ 2024. 01. 16
ํ๋ก ํธ์๋ ์ฑ์ฉ ๊ณผ์ ๋ก ์ํํ Note-App ๊ตฌํ ๊ณผ์ (๊ตฌํ ์ค ์๋ฌ์ฌํญ & ๊ณ ๋ฏผํ ๋ถ๋ถ)์ ์ ๋ฆฌํ ๊ธ์ ๋๋ค.
๐ ์๋ฆฌ์ฆ ์ด์ด๋ณด๊ธฐ
Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (1)
Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (2)
ํ์ฌ๊ธ : Note-App ๊ตฌํ ๊ณผ์ ์ ๋ฆฌ (3)
๐ ๋ชฉ์ฐจ
1. ์ธ๋ถ ํ์ ์ ์ ํ์ผ ๋ง๋ค๊ธฐ
2. ๋ ธํธ ์ญ์ ์ ๋น๋๊ธฐ ์ฝ๋ ์์ฑํ๊ธฐ (async / await)
3. ๋ฐ๋ณต๋๋ handleCreateNote ์ปค์คํ ํ ์ผ๋ก ๋ง๋ค๊ธฐ
4. TypeScript์ Date ํ์
5. vercel ๋ฐฐํฌ
+ ๊ทธ๋ ๊ฒ ๋ง์ง๋ง ๋ , ๊ณผ์ ์ ์ถ์ผ์ด ๋์๋ค.
1. ์ธ๋ถ ํ์ ์ ์ ํ์ผ ๋ง๋ค๊ธฐ
์ฌ๊ธฐ์ LexicalNode.d.ts์ __text์ ํ์ ์ด ์ ์๋์ด ์์ง ์์ ์๋ฌ๊ฐ ๋ฌ๋ค. textNode๋ฅผ ๊ฐ์ ธ์์ผ ํ๊ธฐ ๋๋ฌธ์ ์ธ๋ถ ํ์ ์ ์ ํ์ผ์ ๋ฐ๋ก ๋ง๋ค๋ฉด ๋ ๊ฒ ๊ฐ์ ํ์ ์ ์ ํ์ผ์ ๋ง๋ค์๋ค. ํ์ ์ ์ ํ์ผ์ ์์ฑ์ chatGPT์ ๋์์ ๋ฐ์๋ค.
// lexical-extensions.d.ts
// ์๋์ LexicalNode ํด๋์ค๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
import { LexicalNode as OriginalLexicalNode } from "lexical";
// LexicalNode ํด๋์ค๋ฅผ ํ์ฅํฉ๋๋ค.
declare module "lexical" {
interface LexicalNode {
__text?: string;
// ํ์ํ ๊ฒฝ์ฐ ๋ค๋ฅธ ์์ฑ์ด๋ ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
}
}
// ์๋์ LexicalNode๋ฅผ ํ์ฅํ์ฌ ์ ํด๋์ค๋ฅผ ๋ง๋ญ๋๋ค.
export declare class ExtendedLexicalNode extends OriginalLexicalNode {
// ํ์ํ ๊ฒฝ์ฐ ์ถ๊ฐ ์์ฑ์ด๋ ๋ฉ์๋๋ฅผ ์ฌ๊ธฐ์ ์ถ๊ฐํฉ๋๋ค.
}
chatGPT์ ๋์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๊ณ , ์ค๋ฅ์์ด ๋์ํ๊ธฐ๋ ํ๋๋ฐ ์ด ๋ฐฉ๋ฒ์ด ๋ง๋ ๋ฐฉ๋ฒ์ธ์ง๋ ๋ ์ฐพ์๋ด์ผ ํ ๊ฒ ๊ฐ๋ค.
2. ๋ ธํธ ์ญ์ ์ ๋น๋๊ธฐ ์ฝ๋ ์์ฑํ๊ธฐ (async / await)
const handleRemoveNote = async (targetId: number) => {
if (window.confirm("๋
ธํธ๋ฅผ ์ญ์ ํ์๊ฒ ์ต๋๊น?")) {
await removeNote(targetId);
navigate("/");
}
};
์ฌ๊ธฐ์ navigate๋ ๋น๋๊ธฐ ์์ ์ ํด์ฃผ์ง ์์ผ๋ ์๋์ด ๋์ง ์์๋ค.
์ฌ์ค ์ด ๋ถ๋ถ์ ์์ธ์ง ์ดํด๊ฐ ์ ๋์ง ์์์ ์ฐพ์๋ดค์๋๋ฐ, ์์ง ๋ช ํํ ์ ๋ชจ๋ฅด๊ฒ ๋ค. ๋ ์ฐพ์๋ด์ผ ํ ๊ฒ ๊ฐ๋ค.
3. ๋ฐ๋ณต๋๋ handleCreateNote ์ปค์คํ ํ ์ผ๋ก ๋ง๋ค๊ธฐ
๋ ธํธ๋ฅผ ์์ฑํ๋ handleCreateNote๊ฐ Header์ NoteContainer์์ ํจ๊ป ์ฌ์ฉ๋์๊ธฐ ๋๋ฌธ์ custom hook useCreateNote๋ฅผ ๋ง๋ค์ด ๋ฐ๋ณต๋๋ ์ฝ๋๋ฅผ ํฉ์ณ ์ฝ๋๋์ ์ค์ด๊ณ , ์ฌ์ฌ์ฉ์ฑ์ ๋์๋ค. (์ฌ์ค ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฌ์ฉํ ์ผ์ ์์ ๊ฒ ๊ฐ๊ธฐ๋ ํ์ง๋ง)
๋ ธํธ๋ฅผ ์์ฑํ๋ ํจ์ ๋ฟ๋ง ์๋๋ผ ๋ ธํธ๋ถ์ ์์ฑํ๋ ํจ์ ๋ํ ์ปค์คํ ํ ์ผ๋ก ๋บ ์ ์์ ๊ฒ ๊ฐ์๋ค. ์ข ๋ ์๊ฐํด๋ณด๋ฉด ๋ ธํธ์ ๋ ธํธ๋ถ์ ์์ฑํ๋ ํ๋์ custom hook์ ๋ง๋ค ์ ์์ผ๋ ค๋..? ์ด ๋ถ๋ถ์ ์ข ๋ ์๊ฐํด๋ด์ผ ํ ๊ฒ ๊ฐ๋ค.
// useCreateNote.ts
// Custom Hook
import { useStore } from "../store/notebooks";
const useCreateNote = () => {
const { createNote } = useStore();
const handleCreateNote = (notebook: string) => {
const initialContent = JSON.stringify({
root: {
children: [
{
children: [],
direction: null,
format: "",
indent: 0,
type: "paragraph",
version: 1,
},
],
direction: null,
format: "",
indent: 0,
type: "root",
version: 1,
},
});
const note = {
id: Date.now(),
title: "New Note",
content: initialContent,
date: new Date().toLocaleString(),
};
createNote(notebook, note);
return note.id;
};
return { handleCreateNote };
};
export default useCreateNote;
// ๊ธฐ์กด Header ์ปดํฌ๋ํธ์ handleCreateNote
// Before
import { useParams, useNavigate } from "react-router-dom";
import { useStore } from "../store/notebooks";
import ActionButton from "../componenets/common/ActionButton";
const Header = () => {
const { createNote } = useStore();
const navigate = useNavigate();
const { notebook } = useParams();
const handleCreateNote = () => {
if (notebook) {
const initialContent = JSON.stringify({
root: {
// ...initialContent
},
});
const note = {
id: Date.now(),
title: "New Note",
content: initialContent,
date: new Date().toLocaleString(),
};
createNote(notebook, note);
navigate(`${notebook}/${note.id}`);
} else {
window.alert("Create a Notebook first, please.");
}
};
return (
<HeaderWrapper>
<ButtonArea>
<ActionButton handleClick={handleCreateNote}>New Note</ActionButton>
</ButtonArea>
</HeaderWrapper>
);
};
export default Header;
// custom hook์ธ useCreateNote๋ฅผ ์ฌ์ฉํ Header ์ปดํฌ๋ํธ์ handleCreateNoteClick
// After
import { useParams, useNavigate } from "react-router-dom";
import useCreateNote from "../hooks/useCreateNote";
import ActionButton from "../componenets/common/ActionButton";
const Header = () => {
const navigate = useNavigate();
const { notebook } = useParams();
const { handleCreateNote } = useCreateNote();
const handleCreateNoteClick = () => {
if (notebook) {
const createdNoteId = handleCreateNote(notebook);
navigate(`${notebook}/${createdNoteId}`);
} else {
window.alert("Create a Notebook first, please.");
}
};
return (
<HeaderWrapper>
<ButtonArea>
<ActionButton handleClick={handleCreateNoteClick}>New Note</ActionButton>
</ButtonArea>
</HeaderWrapper>
);
};
custom hook์ ๋ง๋ค๋ฉด์ chatGPT์ ๋์์ ๋ง์ด ๋ฐ์๋๋ฐ, chatGPT์ ์ฌ์ฉ๋น๋๊ฐ ๋์ด๊ฐ๋ฉด์ ๊ฑฑ์ ์ด ๋๋ฉด์๋ chatGPT๊ฐ ํ ์ ์๋ ์ผ๋ค์ ๋๋๊ธฐ๋ ํ๋ฉด์๋ ๋์์ ์์ผ๋ก ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ๋ฐ์ ๊ณต๋ถํด์ผ ํ๋์ง ๊ณ ๋ฏผ์ด ๋๋ ์์ ์ด์๋ค.
4. TypeScript์ Date ํ์
export interface NoteType {
id: number;
title: string;
content: string;
date: string | Date;
}
์ด ๋ถ๋ถ์ ์ด์ ํ ํ๋ก์ ํธ์์๋ ํท๊ฐ๋ ธ๋ ๋ถ๋ถ์ด์๋ค. date๋ฅผ Date ํ์
์ผ๋ก ์ ์ฅํ ์ ์๋ค๊ณ ํ๋๋ฐ, date์ Date ํ์
์ ๋ฃ์ผ๋ ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ date์ ํ์
์๋ฌ๊ฐ ๋ฌ์๋ค. Type 'string | Date' is not assignable to type 'ReactNode'.
ReactNode ํ์ ์๋ ReactElement, string, number, boolean, null, undefined ๋ฑ์ด ์์ง๋ง Date ๊ฐ์ฒด๋ ReactNode์ ํ์ ์ผ๋ก ์ ํจํ์ง ์๋ค. ๋ฐ๋ผ์ ํ์ ์ด Date๊ฐ ๋๋ค๋ฉด, ์ด๋ฅผ string ๋ฑ์ ํ์์ผ๋ก ๋ณ๊ฒฝํด ์ฃผ์ด์ผ ํ๋ค.
<NoteListItemDate>{note.date.toLocaleString()}</NoteListItemDate>
5. vercel ๋ฐฐํฌ
๋ฐฐํฌ๋ ์๊ตฌ์ฌํญ์ ์๊ธฐ๋ ํ์ง๋ง ๋ฐฐํฌ์ฃผ์๊ฐ ์์ผ๋ฉด ๊ตฌํ ์ฌํญ์ ํ์ธํ๊ธฐ ํจ์ฌ ํธํ ๊ฒ ๊ฐ์ ๋ฃ๊ธฐ๋ก ํ๋ค. ์ฒ์์๋ github pages๋ก ๋ฐฐํฌ๋ฅผ ํ๋ ค๊ณ ํ๋๋ฐ, ์ด ๋ถ๋ถ ๋ํ ์ธํ ์ด ํ์ํด์ ์ข ๋ ํธ๋ฆฌํ vercel๋ก ๋ฐฐํฌ๋ฅผ ํด๋ณด๊ธฐ๋ก ํ๋ค.
vercel๋ก ๋ฐฐํฌ๋ฅผ ํ๋ฉด main branch์ push๋ฅผ ํ ๋๋ง๋ค ์๋์ผ๋ก ๋ฐฐํฌ๊ฐ ๋๋๋ก ์ค์ ํ ์ ์์ด ๊ต์ฅํ ํธ๋ฆฌํ๋ค.
{
"rewrites": [
{"source": "/(.*)", "destination": "/"}
]
}
vercel๋ก ๋ฐฐํฌ ํ ํ์ด์ง ์๋ก๊ณ ์นจ์ ํ๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๊ฒ์์ ํด๋ณด๋ ๋ง์ ์ฌ๋๋ค์ด ๊ฒช์ ๋ฌธ์ ์๊ณ , ํด๊ฒฐ๋ฐฉ๋ฒ๋ ์ ์๋ด๋์ด ์์๋ค. ์์ธํ ์ด์ ๋ ์ฐพ์๋ด์ผ ์๊ฒ ์ง๋ง, ํด๋ ์ต์๋จ์ ์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ ๋ ์ด์ ์๋ฌ๊ฐ ๋์ง ์์๋ค ๐
๊ทธ๋ ๊ฒ ๋ง์ง๋ง ๋ , ๊ณผ์ ์ ์ถ์ผ์ด ๋์๋ค.
์์ธ๋ก ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ, ์ญ์ , ์์ ํ๋ ๋ฑ์ CRUD ์์ ๋ณด๋ค lexical editor๋ฅผ ์ฌ์ฉํ๋ค๋์ง, ๋ ์ด์์์ ์ง ๋ค๋์ง ๋ฑ์ ๋ค๋ฅธ ์์ ๋ค์ด ์๊ฐ์ด ํจ์ฌ ๋ง์ด ๊ฑธ๋ ธ๊ณ , ์ด๋ ค์ ๋ค. ํ์ง๋ง ์ค๋๋ง์ ์ด๋ฐ ๊ณผ์ ๋ฅผ ๋ฐ์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ง๋๋ ์์ ์์ฒด๊ฐ ๋๋ฌด ์ฌ๋ฏธ์์๊ณ , ๋ง์๊ฑธ ๋ฐฐ์ธ ์ ์์ด ์๋ฏธ์์๋ค. ์ ํ๋ ์๊ฐ ์์ ์๊ตฌ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ๋ฌด์ธ๊ฐ๋ฅผ ๋ง๋๋ ์ง๋ฆฟํจ ๋ํ ์์๋ค. ์์ธ๋ก ์ด๋ฐ ๋ง๊ฐ๊ธฐํ์ด ๋์๊ฒ ์๋นํ ๋์ ์ง์ค๋ ฅ๊ณผ ๋๊ธฐ๋ถ์ฌ๊ฐ ๋๊ธฐ๋ ํ๋ค.
๋๋ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ชจ๋ฅด๋ ๋ถ๋ถ, ์๋กญ๊ฒ ๋ฐฐ์ด ๋ถ๋ถ๋ค์ ๋ค ๊ธฐ๋กํด๋๋ ํธ์ด๋ค. ๋ฌผ๋ก ํ๋ก์ ํธ ์งํ ์ค์๋ ์ ์ ์ด ์์ด ๋ง๊ตฌ์ก์ด๋ก ๊ฐ๋จํ ๋ฉ๋ชจ ์ ๋๋ง ๋จ๊ธฐ๊ธฐ ๋๋ฌธ์ ์ค๊ตฌ๋จ๋ฐฉ์ด ๋์ด์์ ๋๊ฐ ๋ง๋ค. ๊ณผ์ ๋ฅผ ์ ์ถํ๊ณ ๋ง๊ตฌ์ก์ด๋ก ๋ฉ๋ชจ๋ง ๋จ๊ฒจ๋จ๋ ๋ถ๋ถ๋ค์ ์ ๋ฆฌํ๋ฉฐ ๋ฌด์์ ํ๋์ง ๋ค์ ๊ธฐ์ตํ๊ณ ๊ธฐ๋กํ ์ ์์ด ์ด ์๊ฐ์ ํญ์ ์๋ฏธ์๋ ์๊ฐ์ด๋ผ๋ ์๊ฐ์ด ๋ ๋ค. ํ์ ์์ ์ผํ๊ฒ ๋๋ฉด, ์ด๋ ๊ฒํ๊ธฐ ์ฝ์ง ์์ผ๋ ค๋. ๊ฒฝํํด๋ณด๊ณ ์ถ๋ค, ์๋ก์ด ์ผ์.
๋ง์ง๋ง์ผ๋ก ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์ ๋ฆฌํ๋ฉฐ ๋ง์ฝ ๋ฆฌํฉํ ๋ง ๋๋ ๊ธฐ๋ฅ ๊ฐ์ ๋๋ ์ถ๊ฐ๋ฅผ ํ๋ค๋ฉด ๋ฌด์์ ๊ฐ์ ํ ์ ์์๊น ์๊ฐ๋ ํด๋ณด์๋ค.
- ์๋ฌ์ฒ๋ฆฌ (์๊ฐํด๋ณด๋ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ๋ฐ๋ก ํ ๋ถ๋ถ์ด ์๋ ๊ฒ ๊ฐ๋ค.)
- ์ต์ ํ์ ํ ์คํธ์ฝ๋ ์์ฑ (์ต์ ํ์ ํ ์คํธ์ฝ๋ ์์ฑ์ ํ์ต ๋ชฉ์ ์ผ๋ก ํด๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.)
- svg๋ก ์ด๋ฏธ์ง ์ฝ์ (์ค๊ฐ์ ํ๋ค๊ฐ ์ธํ ์ด ์ ์๋์ ์ผ๋จ ๋์๋ค…)
- ๋ ธํธ๋ถ ์์ฑ ์ปค์คํ ํ ์ผ๋ก ๋ง๋ค๊ธฐ
- Modal์ ์์น๋ ๋ค์ ๊ณ ๋ฏผํด๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.
- ํ์ฌ notebook, note์ id๋ฅผ Date.now()์ random number๋ก ์์ฑํ์๋๋ฐ, uuid ๋ฑ ์ข ๋ ๊ณ ์ ํ๊ณ ์ ๋ํฌํ ๊ฐ์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.