Todo 목록 조회 코드를 TypeScript로 바꾸는 과정

이번 글에서는 JavaScript로 작성한 Todo 목록 조회 코드를 TypeScript로 바꾸는 과정을 정리해보겠습니다.
목표는 단순히 문법만 바꾸는 것이 아니라, 다음과 같은 부분을 더 안전하게 만드는 것입니다.
- API 응답 구조에 타입 붙이기
- Todo 배열의 타입 정의하기
- DOM 요소가 null일 수 있는 문제 처리하기
token값이 없을 수 있는 상황 처리하기- 반복문 안에서
todo.title,todo.id를 안전하게 사용하기
1. 원래 JavaScript 코드
먼저 기존 코드는 다음과 같은 형태입니다.
const getTodoList = async function(){ const response = await fetch( "https://api.fullstackfamily.com/api/edu/ws-283fc1/todos", { method: "GET", headers: { Authorization: `Bearer ${token}`, } }, ); const bodyJson = await response.json(); todoArray = bodyJson.data; if (todoArray.length === 0) { messageEl.textContent = "할 일이 없습니다."; return; } for(let i = 0; i < todoArray.length; i++){ const todo = todoArray[i]; const li = document.createElement("li"); const titleDiv = document.createElement("div"); li.appendChild(titleDiv); titleDiv.textContent = todo.title; todoListEl.appendChild(li); console.log(todo.id); console.log(todo.title); } };
겉보기에는 잘 동작할 수 있지만, TypeScript로 옮기려면 몇 가지를 먼저 생각해야 합니다.
2. 어떤 점을 타입으로 보완해야 할까?
이 코드에서 TypeScript가 특히 중요하게 보는 부분은 다음과 같습니다.
2-1. token은 항상 문자열일까?
localStorage.getItem("token")의 결과는 string | null 입니다.
즉, 토큰이 없을 수도 있습니다.
2-2. response.json()의 결과 구조는 무엇일까?
JavaScript에서는 그냥 bodyJson.data라고 꺼내 쓰지만, TypeScript는 data 안에 어떤 값이 들어 있는지 알아야 합니다.
2-3. todoArray 안의 각 항목은 어떤 모양일까?
todo.id, todo.title을 사용하려면 Todo 객체의 구조를 정의해야 합니다.
2-4. messageEl, todoListEl은 항상 존재할까?
document.getElementById()는 HTMLElement | null 을 반환하기 때문에 null 체크가 필요합니다.
3. Todo 타입 정의하기
먼저 Todo 한 개의 구조를 타입으로 정의합니다.
interface Todo { id: number; title: string; done?: boolean; }
여기서 done은 있을 수도 있고 없을 수도 있다고 가정해서 선택 속성으로 둘 수 있습니다.
이제 API 응답 구조도 정의해봅니다.
interface TodoListResponse { data: Todo[]; }
이렇게 하면 bodyJson.data가 Todo[]라는 사실을 TypeScript가 이해할 수 있습니다.
4. DOM 요소를 타입과 함께 가져오기
다음으로 DOM 요소를 가져올 때 타입을 조금 더 분명히 해봅니다.
const messageEl = document.getElementById("message"); const todoListEl = document.getElementById("todoList");
하지만 이 코드만으로는 TypeScript가 messageEl, todoListEl이 null이 아닌지 확신할 수 없습니다.
그래서 다음처럼 확인해주는 것이 안전합니다.
if (!messageEl || !todoListEl) { throw new Error("필요한 DOM 요소를 찾을 수 없습니다."); }
이제 아래 코드에서는 두 요소가 존재한다고 보고 사용할 수 있습니다.
5. 배열 변수에도 타입 붙이기
기존 코드의 todoArray도 타입을 명확히 해줍니다.
let todoArray: Todo[] = [];
이제 todoArray[i].title, todoArray[i].id 같은 접근이 안전해집니다.
6. 함수의 반환 타입 정하기
비동기 함수는 Promise를 반환합니다.
따라서 getTodoList 함수는 다음과 같이 작성할 수 있습니다.
const getTodoList = async (): Promise<void> => { // ... };
이렇게 하면 이 함수가 값을 반환하지 않고 비동기 작업만 수행한다는 사실이 분명해집니다.
7. fetch 코드에서 token 처리하기
가장 중요한 부분 중 하나가 Authorization 헤더입니다.
기존 JavaScript에서는 이렇게 쓰고 있었습니다.
Authorization: `Bearer ${token}`
그런데 TypeScript에서는 token이 null일 수도 있기 때문에, 먼저 확인하는 과정이 필요합니다.
const token = localStorage.getItem("token"); if (!token) { location.href = "login.html"; return; }
이 체크를 통과한 뒤에는 token이 문자열로 좁혀지므로 안전하게 사용할 수 있습니다.
8. TypeScript로 바꾼 전체 코드
이제 위의 내용을 반영해서 전체 코드를 작성해보겠습니다.
interface Todo { id: number; title: string; done?: boolean; } interface TodoListResponse { data: Todo[]; } const messageEl = document.getElementById("message"); const todoListEl = document.getElementById("todoList"); if (!messageEl || !todoListEl) { throw new Error("필요한 DOM 요소를 찾을 수 없습니다."); } let todoArray: Todo[] = []; const getTodoList = async (): Promise<void> => { const token = localStorage.getItem("token"); if (!token) { location.href = "login.html"; return; } const response = await fetch( "https://api.fullstackfamily.com/api/edu/ws-283fc1/todos", { method: "GET", headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { messageEl.textContent = "할 일 목록을 불러오지 못했습니다."; return; } const bodyJson: TodoListResponse = await response.json(); todoArray = bodyJson.data; if (todoArray.length === 0) { messageEl.textContent = "할 일이 없습니다."; return; } messageEl.textContent = ""; todoListEl.innerHTML = ""; for (let i = 0; i < todoArray.length; i++) { const todo = todoArray[i]; const li = document.createElement("li"); const titleDiv = document.createElement("div"); li.appendChild(titleDiv); titleDiv.textContent = todo.title; todoListEl.appendChild(li); console.log(todo.id); console.log(todo.title); } };
9. 무엇이 좋아졌을까?
TypeScript로 바꾸고 나면 다음과 같은 장점이 있습니다.
9-1. API 응답 구조를 예측할 수 있다
TodoListResponse를 정의했기 때문에 bodyJson.data가 무엇인지 바로 알 수 있습니다.
9-2. Todo 객체의 속성을 안전하게 사용할 수 있다
todo.title, todo.id를 사용할 때 오타나 잘못된 속성 접근을 줄일 수 있습니다.
9-3. null 문제를 미리 잡을 수 있다
messageEl, todoListEl, token처럼 실제 실행 중 자주 문제가 되는 부분을 사전에 처리할 수 있습니다.
9-4. 협업할 때 코드 이해가 쉬워진다
이 함수가 어떤 데이터를 받고 어떤 구조로 동작하는지 타입만 봐도 이해하기 쉬워집니다.
10. 마무리
이번 예제에서는 Todo 목록 조회 코드를 TypeScript로 변환하면서 다음을 정리했습니다.
interface Todo정의- API 응답 타입 정의
token의string | null처리- DOM 요소 null 체크
- 비동기 함수 반환 타입 명시
- 목록 렌더링 코드 안전하게 작성
처음에는 조금 번거롭게 느껴질 수 있지만, 프로젝트가 커질수록 이런 타입 정의가 정말 큰 도움이 됩니다.
다음 글에서는 이 Todo 목록에 체크박스 추가하기, 완료 여부 표시하기, 삭제 버튼 붙이기 같은 기능도 TypeScript 방식으로 확장해볼 수 있습니다.

댓글
댓글을 작성하려면 이 필요합니다.