일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- firestoredatabase
- Github Pages
- Boostrap
- CSS
- db
- url
- http
- Database
- Fetch
- til
- IntersectionObserver
- Protocol
- jQuery
- data
- nosql
- API
- supabase
- Cloud
- TMDB
- HTML
- W
- bootstrap
- JavaScript
- github
- REACT
- useEffect
- this
- web
- 배포
- SQL
- Today
- Total
072DATA
TodoList 만들기 본문
오늘은 TypeScript를 학습하는 과정에서 TodoList를 만들었으며
그 과정을 기록해도록 하겠습니다.
프로젝트 세팅
TypeSript 프로젝트 생성은 생략하며,
먼저 API 요청을 하기위해서 JSON 서버를 Install 합니다
npm install json-server
설치를 완료한 뒤 root 경로에 db.json 파일을 추가하여 키와 값을 입력합니다, 형식은 아래와 같습니다!
{
"todos": [
{
"id": "1",
"text": "할 일 1",
"completed": false
}
]
}
또 원할한 프로젝트 구현을 위하여 json-server 명령어를 .pakage.json의 scripts에 추가합니다
"scripts": {
//...
"server": "json-server --watch db.json --port 4000"
},
그럼 이제 본격적으로 시작!
Todo.ts 생성
Todo.ts을 생성하는데 이 파일의 역할은
타입 정의, API 호출, 함수를 정의하는 역할입니다!
import axios from "axios";
export type Todo = {
id: string;
text: string;
completed: boolean;
};
export type ToggleTodo = Omit<Todo, "text">;
export async function getTodoList(): Promise<Todo[]> {
try {
const response = await axios.get<Todo[]>("http://localhost:4000/todos");
return response.data;
} catch (error) {
console.error("Todo GET 요청 에러:", error);
return [];
}
}
그 후 TypeScript를 컴파일 해줘야 하는데 그 이유는 다음과 같습니다
=> TypeScript 코드는 브라우저에서 직접 실행할 수 없기 때문에
JavaScript로 컴파일해야 합니다 다음 명령어로 컴파일 해줍니다
//컴파일
tsc todo.ts
//실행
node todo.js
이제 이 파일을 가지고 컴포넌트에서 TodoList를 구현하면 됩니다!
컴포넌트 분리
컴포넌트는 두개정도 분리가 됩니다!
TodoList.tsx와 TodoItem.tsx로 분리하면 되는데요
물론 App.tsx에서 이 컴포넌트를 만들어도 상관은 없지만
작은 프로젝트를 임하면서도 유지 보수나 코드 가독성
=> 바로 단일 책임 원칙을 잘 숙지하기 위해서 나누어 보았습니다.
폴더 구조
src/
├── components/
│ ├── TodoList.tsx
│ └── TodoItem.tsx
├── App.tsx
└── ...
구조는 이정도로 잡아주면 될 것 같습니다.
App.tsx
먼저 해야할 일을 추가하기 위한 입력 폼을 넣어주고 하위 컴포넌트인 TodoList로 데이터를 넘겨주어야 합니다.
return (
<>
<h1>TODO LIST</h1>
<input type="text" onChange={handleTextChange} />
<button onClick={handleAddTodo}>추가하기</button>
<TodoList
todoList={todoList}
/>
</>
);
코드를 보면 Text 변경 및 새로운 Todo를 Add하는 함수가 존재하며 todoList라는 props가 존재합니다
따라서 필요한 상태를 선언하면 됩니다
const [todoList, setTodoList] = useState<Todo[]>([]);
const [text, setText] = useState("");
useState옆에 <Todo[]>라는 타입이 지정되어 있는데 이는 앞서 생성했던 Todo.ts에서
정의된 타입입니다 따라서 <Todo[]>는 Todo 타입의 객체 배열이어야 한다는 뜻입니다!
그 다음 text 상태를 보면 아무런 타입도 지정되어 있지 않은데
초기값에 빈 문자열 (" ") 을 넣어주면 TypeScript는 해당 상태를 String 으로 타입 추론을 하게 됩니다!
물론 명시적으로 type을 지정해주면 되지만 타입 추론 개념도 가져가고 싶어서 기록해 보았습니다.
Todo 추가하기
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
};
const handleAddTodo = async () => {
const newTodo: Todo = {
id: crypto.randomUUID(),
text,
completed: false,
};
try {
const response = await axios.post<Todo>(
"http://localhost:4000/todos",
newTodo
);
setTodoList((prev) => [...prev, response.data]);
setText("");
} catch (error) {
console.error("Todo 등록 에러:", error);
}
};
입력된 내용에 따라 text의 값을 변경하는 함수와 Todo를 등록하는 함수를 선언해줍니다.
newTodo도 Todo 타입으로 정의된 객체인 것을 볼 수 있으며
axios.post에서 응답 데이터의 타입도 Todo로 지정해줍니다
뭔가 이쯤되니 타입을 지정해주는게 오히려 더 귀찮은 기분이 듭니다..ㅎㅎ
type TodoListProps = {
todoList: Todo[];
//...
};
const TodoList = ({
todoList,
//...
}: TodoListProps) => {
그 다음 TodoList 컴포넌트에서 props로 전달받은 todoList마저 Todo 객체의 배열 타입으로 지정해주고 있는데
상태를 선언할 때 Todo[]로 타입을 지정해줬는데 왜 또 여기서 타입을 지정해줘야 하는지 의문이 들었습니다..
G선생님께 한 번 여쭈어보니 이러한 답변을 해주셨어요..
- 스코프의 독립성: todoList는 상태에서 정의된 것과 props에서 정의된 것이 서로 다른 스코프에서 사용됩니다. 상태는 해당 컴포넌트 내부에서 관리되고, props는 외부에서 전달됩니다.
- 타입 안정성: 각 스코프에서 todoList가 어떤 타입인지 명확하게 정의함으로써, 코드의 안정성을 높이고, 잘못된 타입의 데이터가 들어오는 것을 방지할 수 있습니다.
- 가독성 및 유지보수: 컴포넌트가 어떤 props를 받는지 명확하게 알 수 있어, 코드 가독성이 향상되고 유지보수가 쉬워집니다.
근데 솔직히 이러한 이유가 있다고 해도 굳이란 생각이 드는 건 마찬가지
어쨌든 props로 전달받았기 때문에 그 부모 컴포넌트의 state 타입만 숙지하면 되는 것 아닌가 싶네욥...ㅠ
Todo 삭제 및 업데이트
const handleDeleteTodo = async (id: Todo["id"]) => {
try {
await axios.delete(`http://localhost:4000/todos/${id}`);
setTodoList((prev) => prev.filter((todo) => todo.id !== id));
} catch (error) {
console.log("Todo 삭제 에러:", error);
}
};
const handleUpdateTodo = async ({ id, completed }: ToggleTodo) => {
try {
console.log("눌림");
await axios.patch(`http://localhost:4000/todos/${id}`, {
completed: !completed,
});
setTodoList((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, completed: !completed } : todo
)
);
} catch (error) {
console.log("Todo 수정 에러:", error);
}
};
여기서 인자로 받는 id 값을 Todo 객체의 id의 타입으로 지정해주는 삭제 함수와
id, completed 두개의 인자를 받는 함수인 업데이트 함수가 있는데
여기서 핵심은 `text` 속성 빼고 나머지를 다 받아오고 있는 업데이트 함수입니다.
여기서 유틸리티 타입중 하나인 Omit을 사용하여 유연하게 타입을 지정해줄 수 있어요
따라서 ToggleTodo는 Todo.ts에 있는 제네릭 타입을 사용한 타입 별칭이었으며 바로 임포트해서 사용합니다!!
props 타입 지정
//TodoList.tsx
type TodoListProps = {
todoList: Todo[];
onDeleteClick: (id: Todo["id"]) => void;
onToggleClick: (toggleTodo: ToggleTodo) => void;
};
const TodoList = ({
todoList,
onDeleteClick,
onToggleClick,
}: TodoListProps) => {
return (
//....
//TodoItem.tsx
type TodoItemProps = Todo & {
onDeleteClick: (id: Todo["id"]) => void;
onToggleClick: (toggleTodo: ToggleTodo) => void;
};
const TodoItem = ({
id,
text,
completed,
onDeleteClick,
onToggleClick,
}: TodoItemProps) => {
//.....
그리고 컴포넌트로 내려줄 떄 똑같이 타입을 지정해줍니다..왜 굳이 또 지정하냐고
그후 작스 문법에서 todoList를 map으로 돌려 데이터를 뿌려주고
필요한 부분에 props로 내려준 함수를 사용해주면 TodoList가 완성됩니다!!
결과물
간단하쥬 ??ㅎㅎ
마치며
내 생각보다 훨씬 더 복잡한 타입스크립트... 솔직히 개인적으로 비효율 적인 부분도 있는 것 같음
가독성이 그리 좋지도 않은 것 같고 근데 분명 자바스크립트를 더 안정적으로 사용할 수는 있을 것 같아서
여러가지 라이브러리를 활용하면 확실히 더 쉽고 든든한 언어일 것 같긴 함 낼도 홧팅..
'FrontEnd > TypeScript' 카테고리의 다른 글
모달창 만들기(리액트, TypeScript) (0) | 2024.10.10 |
---|---|
TypeScript 타입 선언하는 방법 (0) | 2024.09.25 |
타입스크립트 요약 (0) | 2024.09.24 |