- ํ๋ก์ ํธ ๋ช : Todo App with Next.js and TypeScript
- ํ๋ก์ ํธ ๊ธฐ๊ฐ: 2022. 8. 27. ~ 2022. 9. 1
git clone https://github.com/hjpark625/todo-next.git
cd todo-next
yarn install
yarn start
- ์ ์ ์ด์
- Next.js
- ์๋ฒ์ฌ์ด๋๋ ๋๋ง(SSR)์ ํ์ฉํ์ฌ ํด๋ผ์ด์ธํธ์ฌ์ด๋๋ ๋๋ง(CSR)๋ณด๋ค ์ฐ์ํ ์๋์ ๋ ๋๋ง์ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
- CSR์ ํ ๋น์ด์๋ HTMLํ์ผ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ค์ด๋ก๋๋ฐ๊ณ ๊ทธ ์ดํ ์คํฌ๋ฆฝํธ๋ฅผ ๋ก๋ํด HTML์ ๊ทธ๋ ค๋ด๋ ๋ฐฉ์์ธ ๋ฐ๋ฉด SSR์ ์๋ฒ์์ฒด์์ HTML์ ๋ชจ๋ ์คํฌ๋ฆฝํธ๋ค์ ๊ทธ๋ ค์ ํด๋ผ์ด์ธํธ์ ๋ด๋ณด๋ด๋ ๋ฐฉ์์ผ๋ก ํด๋ผ์ด์ธํธ์์ ์ผ์ ๋ ๋ฒ ํ ๊ฒ์ ํ ๋ฒ์ผ๋ก ์ค์ฌ์ฃผ๋ ์ฅ์ ์ด ์๋ค.
- ์์ ์ค๋ช ์ ๊ทผ๊ฑฐํ์ฌ ๊ฒ์์์ง์ต์ ํ(SEO)์๋ ์ต์ ์ ์ฑ๋ฅ์ ๊ตฌํํ ์ ์๋ค.
- ์๋ฒ์ฌ์ด๋๋ ๋๋ง(SSR)์ ํ์ฉํ์ฌ ํด๋ผ์ด์ธํธ์ฌ์ด๋๋ ๋๋ง(CSR)๋ณด๋ค ์ฐ์ํ ์๋์ ๋ ๋๋ง์ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.
- React
- React๋ ํ์กดํ๋ ์๋ฐ์คํฌ๋ฆฝํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ๋ง์ด ํ์ฉ์ค์ ์๋ค.
- SPA๋ผ๋ ๊ฐ์ ์ด ์กด์ฌํด ์๋ก์ด ํ์ด์ง๋ฅผ ์์ฒญ ํ ๋ ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ๊ฐฑ์ ํจ์ผ๋ก์จ ํธ๋ํฝ ๊ฐ์์ ๋ ๋๋ง์์ ํจ์จ์ ์ด๋ค.
- ๋น ๋ฅธ ํ๋ฉด ์ด๋์ด ๊ฐ๋ฅํ๋ฉฐ ์ฑ์ฒ๋ผ ์์ฐ์ค๋ฌ์ด ๋์์ผ๋ก ์ต์ ์ UX๋ฅผ ์ ๊ณตํ ์ ์๋ค.
- TypeScript
- ์ ์ ํ์
์ง์ํ๋ฏ๋ก ์ปดํ์ผ ๋จ๊ณ์์ ์ค๋ฅ๋ฅผ ์ฌ์ ์ ํฌ์ฐฉํ ์ ์์ผ๋ฉฐ ์ด๋ฅผ ํตํด ๋ฏธ๋ฆฌ ๋๋ฒ๊น
์ด ๊ฐ๋ฅํ๋ค.
- ๊ฐ๋ฐ ๊ฐ ๊ฐ๋ฐ์์ ํด๋จผ ์๋ฌ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์งํ์ฌ ์ถํ ๋ฐฐํฌ ์์ ๋ฌธ์ ๋ฅผ ์ฌ์ ์ ์ผ๋ก ์ฐจ๋จํ๋๋ฐ ํจ๊ณผ์ ์ด๋ค.
- ์ ์ ํ์
์ง์ํ๋ฏ๋ก ์ปดํ์ผ ๋จ๊ณ์์ ์ค๋ฅ๋ฅผ ์ฌ์ ์ ํฌ์ฐฉํ ์ ์์ผ๋ฉฐ ์ด๋ฅผ ํตํด ๋ฏธ๋ฆฌ ๋๋ฒ๊น
์ด ๊ฐ๋ฅํ๋ค.
- Redux / React-Redux
- ์ ์ญ์ํ ๊ด๋ฆฌํ๋๋ฐ ์์ด ํ์ค์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
- FLUXํจํด์ผ๋ก ๋ฐ์ดํฐ ํ๋ฆ์ ๋จ๋ฐฉํฅ์ฑ์ ์ถ๊ตฌํ์ฌ ์ถํ ์๋ฌ๋ฐ์ ์ ์๋ฌ์ ์์ธ์ ํ์ ํ๋๋ฐ ์ฝ๊ณ ์ ์ง๋ณด์์ ๋งค์ฐ ํ๋ ฅ์ ์ด๋ค.
- Styled-Components
- CSS-in-JS๋ ์งง์ ๊ธธ์ด์ ์ ๋ํฌํ ํด๋์ค๋ฅผ ์๋์ ์ผ๋ก ์์ฑํ๊ธฐ์ ์ฝ๋ ๊ฒฝ๋ํ์ ํจ๊ณผ์ ์ด๋ค.
- ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ๊ฐ๋ฐ ๋ฐฉ๋ฒ์ ์ ํฉํ๊ณ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ CSS-in-JS ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- JS ์์์ ํจ์๋ฅผ ์ฝ๊ฒ ๊ณต์ ํ์ฌ props๋ฅผ ํ์ฉํ ์กฐ๊ฑด๋ถ ๋ ๋๋ง์ ์ฉ์ดํ๋ค.
- ์ปดํฌ๋ํธํํ์ฌ ์คํ์ผ์ ์ฌํ์ฉํ๋๋ฐ ์ฉ์ดํ๋ค.
- Next.js
root
|-- tsconfig.json
|-- README.md
|-- next.config.js
|-- package.json
|-- yarn.lock
|-- .gitignore
|-- .babelrc
|-- .github
|-- compoenents
| |-- /Todos
| |-- TodoInsert.tsx
| |-- TodoList.tsx
| |-- TodoListItem.tsx
| |-- TodoTemplate.tsx
|-- pages
| |-- /api
| | |-- hello.ts
| |-- _app.tsx
| |-- index.tsx
| |-- todo.tsx
|-- styles
| |-- GlobalStyle.tsx
| |-- palette.ts
|-- store
|-- types
-
Todo ๋ฆฌ์คํธ ์์ฑ(Create)
-
todo.tsx์์ TODO_DATA๋ผ๋ ์ด๊ธฐ๊ฐ ์ญํ ์ ํ๋ ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์
-
TodoInsert.tsx์์ ์ฌ์ฉ์๊ฐ input์ ์ ๋ ฅํ ๊ฐ์ state์ ์ ์ฅ
/* TodoInsert.tsx */ const [todoValue, setTodoValue] = useState(''); const saveInputValue = useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { setTodoValue(e.target.value); // input์ ์์ฑ๋๋ string์ state์ ์ ์ฅ }, [], );
-
์์ ์ฝ๋๋ฅผ
Redux
๋ก ๋ณ๊ฒฝํ๋ค๋ฉด.../* TodoInsert.tsx */ const saveInputValue = useCallback( (e: React.ChangeEvent<HTMLInputElement>) => { dispatch(changeField(e.target.value)); }, [], ); /* todos.ts */ export const changeField = (value: string) => ({ type: CHANGE_FIELD, value, });
- ๊ธฐ์กด์ setState๋ฅผ ํ์ฉํด value๋ฅผ ์ ์ฅํ๋ค๋ฉด
dispatch
ํจ์๋ฅผ ํ์ฉํดtodos.ts
์ ์๋ ์ก์ ์์ฑํจ์์ธchangeField
๋ฅผe.target.value
๋ฅผ payload์ ๋ด์ ์คํ์ํจ๋ค.
- ๊ธฐ์กด์ setState๋ฅผ ํ์ฉํด value๋ฅผ ์ ์ฅํ๋ค๋ฉด
-
์ ์ฅ๋ value๋ฅผ ์ถ๊ฐ ๋ฒํผ์ ๋๋ ์ ๋ todo.tsx์์ props๋ก ๋ด๋ ค์ฃผ๋
onInsert
ํจ์์ ํฌํจ์์ผ submit/* todo.tsx */ const [todos, setTodos] = useState(TODO_DATA); const nextId = useRef(1); const onInsert = useCallback( (text: string) => { const todo = { id: nextId.current, todo: text, checked: false, }; setTodos(todos.concat(todo)); // ์์๋ฐ์ดํฐ ํ์ฉํด concat์ผ๋ก ์๋ก์ด ๋ฆฌ์คํธ ์ถ๊ฐ nextId.current += 1; // Todo๊ฐ ์ถ๊ฐ๋๋ฉด ref์ ๊ฐ์ 1์ฉ ๋ํจ }, [todos], ); /* ... */ const TODO_DATA = [ { id: 0, text: '', checked: false, }, ];
-
์์ ์ฝ๋๋ฅผ
Redux
๋ก ๋ณ๊ฒฝํ๋ค๋ฉด.../* todo.tsx */ const nextId = useRef(1); const onInsert = useCallback( (text: string) => { dispatch(addTodo(text, nextId)); nextId.current++; // Todo๊ฐ ์ถ๊ฐ๋๋ฉด 1์ฉ ๋ํ๊ธฐ }, [todos], ); /* todos.ts */ export const addTodo = ( inputValue: string, nextId: React.MutableRefObject<number>, ) => ({ type: ADD_TODO, nextId, // ์๋ก๋ค์ด์จ id๊ฐ inputValue, // changeField ํจ์๋ก ์ ์ฅ๋ e.target.value๊ฐ });
-
-
Todo ๋ฆฌ์คํธ ๋ถ๋ฌ์ค๊ธฐ(Read)
- todo.tsx์์
useSelector
๋ฅผ ํ์ฉํด ๋ฐฐ์ด๋ก ๋ง๋ค์ด์งtodos
๋ฅผ props๋ก TodoList.tsx๋ก ์ ๋ฌ ํmap
์ ์ด์ฉํด ์ ์ฅ๋ ๋ฐ์ดํฐ๋ค์ ๋ ๋๋ง
- todo.tsx์์
-
Todo ์์ ํ๊ธฐ(Update)
-
edit๋ฒํผ์ ๋๋ ์ ๋ ์์ input์ฐฝ์ด ๋ํ๋๊ณ ๊ธฐ์กด ๊ฐ์ ๋์ฐ๋๋ก ๊ตฌํ
- ์์ ๋ฒํผ ํด๋ฆญํ ๋
boolean
๊ฐ์ ๊ฐ์ง isEdit์ด๋ผ๋ state๋ฅผ ํ์ฉ
- ์์ ๋ฒํผ ํด๋ฆญํ ๋
-
Createํ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก edit input์ฐฝ์ ์ ๋ ฅ๋๋ string์ state์ ์ ์ฅ
-
์์ ๋ฒํผ ํด๋ฆญ ์ input์ฐฝ์ focus๋๋๋ก
useRef
์useLayoutEffect
๋ฅผ ํ์ฉํด์ ๊ตฌํconst editRef = useRef<HTMLInputElement | null>(null); useLayoutEffect(() => { if (editRef.current !== null) return editRef.current.focus(); });
-
todo.tsx์์
onEdit
ํจ์๋ฅผ ์ ์ํด props๋ก ์ ๋ฌconst onEdit = useCallback( (e: React.FormEvent<HTMLFormElement>, id: number, editTodo: string) => { e.preventDefault(); if (!editTodo) return alert('๋น ์นธ์ผ๋ก ์์ ํ ์ ์์ต๋๋ค.'); // input์ฐฝ์ด ๋น ์นธ์ผ ๋ alert์ฐฝ ํ์ฉ setTodos( todos.map((todo) => todo.id === id ? { ...todo, todo: editTodo } : todo, ), ); // ์๋กญ๊ฒ ์์ ๋๋ ํ ์คํธ๋ฅผ todo ์คํ ์ดํธ์ ๋ฐ์ }, [todos], );
- ์์ ํ ์ํฐ๋ฅผ ์ณค์ ๋ submit์ผ๋ก ์๋กญ๊ฒ ์์ ๋๋ ํ ์คํธ๋ฅผ todos์ ๋ฐ์ํ๊ณ ์์ ๋ ํ ์คํธ๋ฅผ ์ฌ๋ ๋๋ง
-
์์ ์ฝ๋๋ฅผ
Redux
๋ก ๋ณ๊ฒฝํ๋ค๋ฉด.../* todo.tsx */ const onEdit = useCallback( (e: React.FormEvent<HTMLFormElement>, edit_value: string, id: number) => { e.preventDefault(); dispatch(editTodo(edit_value, id)); }, [todos], ); /* todos.ts */ export const editTodo = (edit_value: string, id: number) => ({ type: EDIT_TODO, edit_value, id, });
-
-
Todo ๋ฆฌ์คํธ ์ญ์ ํ๊ธฐ(Delete)
-
filter
๋ฉ์๋๋ฅผ ํ์ฉํด์ ํด๋ฆญํ๋ฉด ํด๋ฆญ ๋ todo์ id์ ํด๋น todo์ id๋ฅผ ๋น๊ตํ์ฌ ์ผ์นํ๋ ๊ฒ ์ ์ธํ ๋๋จธ์ง todo๋ค์ ๋ฝ์๋ด๊ณ ๋๋จธ์ง Todo ๋ฆฌ์คํธ๋ค์ ์ฌ๋ ๋๋งconst onRemove = useCallback( (id: number) => { dispatch(deleteTodo(id)); }, [todos], );
-