백엔드에서 아래와 같이 Response 를 작성했는데…
아래와 같이 작성한 Response 를 프론트에서 어떻게 받고, 처리해야 할지 난감했습니다.
@GetMapping(value = "/{boardId}")
public ResponseEntity<?> displayBoardDetail (@PathVariable("boardId") Long boardId) {
try {
List<BoardDetailResponse> boardDetailResponses = boardService.displayBoardDetail(boardId);
return ResponseEntity.ok(boardDetailResponses);
} catch (ApiException apiException) {
return ResponseEntity.status(apiException.getErrorType().getStatus()).body(apiException.getErrorType().getMessage());
}
}
// BoardDetailResponse
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BoardDetailResponse {
private Long taskBoxId;
private Long taskBoxOrder;
private String taskBoxTitle;
private List<Task> taskDetailResponses;
}
// Task Entity
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String tag;
private LocalDate dueDate;
private String workHour;
private String createdBy;
private Long taskOrder;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "task_box_id")
private TaskBox taskBox;
...
}
Java
복사
그러던 찰나 interface 를 작성하여 관리하는 법을 알게되었고, 아래와 같이 interface 를 작성하였습니다.
Task.ts
export interface Task {
id: number;
title: string;
tag: string;
dueDate: string;
workHour: string;
createdBy: string;
taskOrder: number;
}
TypeScript
복사
BoardDetailResponse.ts
위에서 작성한 Task 를 끌고 오는 것이죠.
import {Task} from "./Task.ts";
export interface BoardDetailResponse {
taskBoxId: number;
taskBoxOrder: number;
taskBoxTitle: string;
taskDetailResponses: Task[];
}
TypeScript
복사
api 연결하기
이제 실제로 api 를 연결해봅시다. 저는 api 는 이런식으로 따로 분류를 해놓는데요,
해당 api 는 BoardApi.ts 에 작성했으므로, 해당 파일을 까보겠습니다.
BoardApi.ts
export const boardDetail = async (boardId: UnwrapRef<() => string>) => {
const response = await axiosInstance.get(`/boards/${boardId}`);
return response;
}
TypeScript
복사
다음은 이 api 를 불러온 컴포넌트로 가보겠습니다!
<script setup lang="ts">
const boardData = ref<BoardDetailResponse[]>([]);
const fetchBoardDetail = async () => {
try {
const response = await boardDetail(boardId.value);
boardData.value = response.data;
} catch (error) {
console.error(error);
}
};
</script>
HTML
복사
사용은 어떻게 하냐구요?
<template>
<div class="board-detail-container">
<div class="board-detail-icon-container">
<div class="board-detail-title">
{{ boardTitle }}
</div>
<div>
<font-awesome-icon style="margin-right: 15px; font-size: 23px" @click="openCreateModal" icon="fa-solid fa-folder-plus" />
<font-awesome-icon style="margin-right: 10px; font-size: 19px" @click="openMemberSearchModal(boardId)" icon="fa-solid fa-user-plus" />
<font-awesome-icon icon="fa-solid fa-user-minus" style="margin-right: 13px; font-size: 19px" @click="openMemberSettingModal"/>
<font-awesome-icon v-if="isMemberHost" icon="fa-solid fa-address-card" style="margin-right: 13px; font-size: 22px" @click="openMemberReportModal"/>
<font-awesome-icon v-if="isMemberHost" icon="fa-solid fa-users-rectangle" style="font-size: 21px" @click="openTeamReportModal"/>
</div>
</div>
<div class="kanban-container">
<!-- 늘 하던대로 반복문을 통해 꺼내오면 됩니다 -->
<div v-for="taskBox in boardData" :key="taskBox.taskBoxId"
class="kanban-column"
draggable="true"
@dragstart="onDragStartTaskBox($event, taskBox.taskBoxId, taskBox.taskBoxOrder)"
@dragover="onDragOver"
@drop="onDropTaskBox($event, taskBox.taskBoxId)">
<div class="kanban-column-header">
<p>
{{ taskBox.taskBoxTitle }}
</p>
<div class="board-button-container">
<font-awesome-icon icon="fa-regular fa-pen-to-square" @click="openUpdateModal(taskBox.taskBoxId)" style="margin-right: 15px;" class="board-icon"/>
<font-awesome-icon icon="fa-solid fa-file-circle-plus" @click="openTaskCreateModal(taskBox.taskBoxId, boardId)" style="margin-right: 15px;" class="board-icon"/>
<font-awesome-icon icon="fa-solid fa-trash-can" @click="handleDeleteTaskBox(taskBox.taskBoxId)" class="board-icon"/>
</div>
</div>
<!-- 늘 하던대로 반복문을 통해 꺼내오면 됩니다 -->
<div v-for="(task, taskIndex) in taskBox.taskDetailResponses" :key="task.id"
class="kanban-row"
draggable="true"
@dragstart="onDragStartTask($event, task.id, task.taskOrder, taskBox.taskBoxId)"
@dragover="onDragOver"
@drop="onDropTaskElement($event, taskBox.taskBoxId, taskIndex)">
<div class="kanban-row-header">
<p>
{{ task.title }}
</p>
<div class="kanban-button-container">
<font-awesome-icon icon="fa-regular fa-pen-to-square" @click="openTaskUpdateModal(task.id)" style="margin-right: 12px;" class="kanban-row-icon"/>
<font-awesome-icon icon="fa-solid fa-trash-can" @click="handleDeleteTask(task.id)" class="kanban-row-icon"/>
</div>
</div>
<div class="kanban-row-content"
@drop="onDropTaskElement($event, taskBox.taskBoxId, taskIndex)">
<div class="kanban-row-content-icon">
<font-awesome-icon style="margin-right: 5px" icon="fa-regular fa-calendar-check" />
<span>{{ task.dueDate }}</span>
</div>
<div class="kanban-row-content-icon">
<font-awesome-icon style="margin-right: 5px" icon="fa-regular fa-clock" />
<span>{{ task.workHour }}</span>
</div>
<div class="kanban-row-footer" style="margin-top: 10px">
<div class="kanban-badge-created">
{{ task.createdBy }}
</div>
<div class="kanban-badge" :class="getTagClass(task.tag)">
{{ task.tag }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
HTML
복사
복잡해보이는데, 어떻게 매핑이 되는걸까?
제가 생성한 칸반보드에 들어가면, 다음과 같이 칸반보드 상세 데이터를 받을 수 있습니다. 지금이야 데이터가 적지만, 사람들이 일 하다 보면 많아지겠죠?
interface 로 처리하면 아래와같이 복잡해보이는 응답받은 데이터를 쉽게 처리할 수 있다는 건 알겠습니다. 하지만 어떤 원리로 interface 와 response 가 매핑이 되는 걸까요?
[
{
"boardTitle": "프로젝트 1 수정",
"boardId": 29,
"members": [
{
"memberId": 36,
"username": "시은",
"role": "Host",
"pending": false
},
{
"memberId": 42,
"username": "듀밍",
"role": "Member",
"pending": true
},
{
"memberId": 43,
"username": "태훈",
"role": "Member",
"pending": true
},
{
"memberId": 45,
"username": "혜빈",
"role": "Member",
"pending": true
}
]
},
{
"boardTitle": "프로젝트 2",
"boardId": 30,
"members": [
{
"memberId": 37,
"username": "시은",
"role": "Host",
"pending": false
}
]
},
{
"boardTitle": "프로젝트 3",
"boardId": 31,
"members": [
{
"memberId": 38,
"username": "시은",
"role": "Host",
"pending": false
}
]
},
{
"boardTitle": "프로젝트 4",
"boardId": 32,
"members": [
{
"memberId": 39,
"username": "시은",
"role": "Host",
"pending": false
}
]
},
{
"boardTitle": "프로젝트 5",
"boardId": 33,
"members": [
{
"memberId": 40,
"username": "시은",
"role": "Host",
"pending": false
}
]
},
{
"boardTitle": "프로젝트 6",
"boardId": 34,
"members": [
{
"memberId": 41,
"username": "시은",
"role": "Host",
"pending": false
}
]
}
]
JSON
복사
API 응답 데이터를 TypeScript 인터페이스로 매핑 하는 원리 이해하기
TypeScript의 타입 시스템
TypeScript 는 JavaScript 에 정적 타입 시스템을 추가합니다. 이는 변수, 매개변수, 반환값 등이 특정 타입을 가진다고 명시적으로 선언할 수 있게 해주는데요 예를 들어, 어떤 함수가 특정 interface(예시에서는 Task, BoardDetailResponse 입니다.) 를 따르는 객체를 반환한다고 선언하면 TypeScript 컴파일러는 이 함수가 해당 인터페이스에 정의된 모든 필드와 타입을 준수하는 객체를 반환하도록 강제합니다.
TypeScript의 구조적 타이핑
TypeScript 는 구조적 타이핑(Structural Typing) 을 사용합니다. 이는 객체의 실제 구조가 인터페이스에 정의된 구조와 일치하는 한, 해당 객체가 인터페이스를 준수한다고 간주한다는 의미입니다. 예를 들어, 인터페이스에 정의된 필드가 객체에 모두 존재하면 TypeScript 는 이 객체가 해당 인터페이스를 구현한다고 간주합니다.
타입을 동적으로 변경할 수 있는 JavaScript
JavaScript 는 동적 타입 언어로, 변수의 타입은 런타임에 결정되고 변경될 수 있습니다. TypeScript 는 이러한 JavaScript 의 특성을 그대로 유지하면서, 컴파일 시점에 타입 검사를 수행해 프로그램의 안정성을 높입니다.
TypeScript 는 API 응답을 어떻게 매핑하는가?
API 로부터 받은 데이터는 일반적으로 JSON 형식이며, JavaScript 객체로 자동 변환됩니다. TypeScript 는 이 객체가 특정 인터페이스를 준수하는지 컴파일 시점에 검사합니다. API 응답 객체가 인터페이스에 정의된 필드와 타입을 모두 갖추고 있다면, TypeScript 는 이 객체를 해당 인터페이스의 인스턴스로 간주합니다.
이렇게 TypeScript 와 api 응답에 대한 지식이 한층 더 상승했습니다.