| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- v8 원리
- Js module
- 웹팩 기본개념
- 알고리즘
- 리액트 메모
- chromatic error
- 타입스크립트
- this
- 리덕스
- 렌더링 최적화
- 항해99
- 리액트 렌더링 최적화
- 함수형 프로그래밍 특징
- 코어자바스크립트
- 리액트 메모이제이션
- 항해99 부트캠프
- 리액트
- 항해99 미니프로젝트
- 자바스크립트 엔진 v8
- FP 특징
- jwt
- JS module system
- js배열 알고리즘
- 웹 크롤링
- 항해99 사전스터디
- toggle-btn
- gql restapi 차이
- 실행컨텍스트
- next js
- 테스트 코드 툴 비교
- Today
- Total
Jaeilit
타입스크립트을 써야하는 이유 본문
배경
JS로만 되어있는 프로젝트를 담당하게 되었습니다. 언어 레벨에서는 프로젝트의 안전성을 생각해서 TS를 도입해야한다고 생각했고 제안을 했습니다. 팀 리더분께서는 처음에 부정적인 시선으로 보셨고, 그래도 제 의견을 조금 고려해주셔서 발표 세션을 준비하라고 하셨습니다.
이 내용은 내부적으로 설득에 이용했던 내용을 회사 코드를 걷어내고 조금 수정한 수정 본입니다.
런타임 오류 방지
자바스크립트의 한계는 타입 관련 오류를 실행 전에 잡아낼 수 없다는 점입니다.
자바스크립트는 동적 타입 언어로, 변수의 타입이 실행 시점(런타임)에 결정됩니다. 예를 들어, 숫자를 기대하는 함수에 문자열을 전달해도 코드를 실제로 실행하기 전까지는 이러한 오류를 발견할 수 없습니다.
이런 한계를 극복하고자 자바스크립트의 슈퍼셋으로 타입스크립트가 등장했습니다. 타입스크립트는 코드 작성 시점과 트랜스파일 시점에서 타입 오류를 사전에 잡아내어 런타임 오류를 방지하는데 도움을 줍니다.
예시 1)
// JavaScript (런타임 오류 가능)
function add(a, b) {
return a + b;
}
add(1, "2"); // "12" (원치 않는 결과)
// TypeScript (코드 작성 시점에 오류 감지)
function add(a: number, b: number): number {
return a + b;
}
add(1, "2"); // ❌ 오류: 'string' 형식을 'number'에 할당할 수 없음
자동완성 기능
자동완성과 IntelliSense는 개발 생산성을 크게 향상시키는 기능입니다. 순수 JavaScript에서도 IDE가 어느 정도 타입 추론을 제공하지만, 추론이 불가능한 경우 `any` 타입으로 표시됩니다. `any` 타입은 "어떤 타입이든 될 수 있다"는 의미로, 이는 곧 타입 안전성이 전혀 보장되지 않는다는 뜻입니다.
제네릭을 활용한 타입 재사용
TypeScript의 제네릭(Generic)은 타입을 변수처럼 사용할 수 있게 해주는 기능입니다. 이를 통해 재사용 가능한 타입 구조를 만들 수 있으며, 특히 백엔드와 협업 시 API Response와 Request Parameter를 타입 안전하게 관리할 수 있습니다.
API 공통 구조 정의
백엔드 API는 보통 일관된 응답 형식을 가집니다.
제네릭을 사용하면 이 공통 구조를 한 번만 정의하고 여러 API에서 재사용할 수 있습니다.
// api.ts - 모든 API가 공유하는 기본 구조
export interface APIResponse {
resData: T; // 실제 데이터 (API마다 다름)
resCode: string; // 응답 코드
resMsg: string; // 응답 메시지
}
export interface APIFnCallback {
onSuccess: (_data: T) => void;
onError: (_data: T) => void;
}
제네릭 <T>의 의미:
T는 Type Parameter로, 실제 사용 시점에 구체적인 타입으로 대체됩니다- 각 API마다 다른 데이터 구조를 유연하게 적용할 수 있습니다
- 타입 안전성을 유지하면서 코드 재사용성을 높입니다
제네릭 활용의 장점
- 타입 재사용성
APIResponse<T>하나로 모든 API 응답 타입 커버- 새로운 API 추가 시 데이터 타입만 정의하면 됨
- 자동완성 지원
response.resData.입력 시 해당 API의 모든 필드 확인 가능- API 명세를 매번 확인할 필요 없음
- 타입 안전성
- 잘못된 필드 접근 시 컴파일 단계에서 오류 감지
- 리팩토링 시 영향받는 모든 코드를 IDE가 표시
- 유지보수 용이
- 공통 구조 변경 시 한 곳만 수정
- API 명세 변경 시 타입만 수정하면 관련 코드에서 오류 표시
전체 예시코드)
// api.ts
// api response format
export interface APIResponse<T> {
resData: T;
resCode: string;
resMsg: string;
}
export interface APIFnCallback<T> {
onSuccess: (_data: T) => void;
onError: (_data: T) => void;
}
// login.ts
import type { APIFnCallback, APIResponse } from '../common/api';
export interface LoginAPIParams {
id: string;
password: string;
}
export interface LoginAPISuccess {
isFirstLogin: boolean;
isPasswordExpired: boolean;
accessToken: string;
}
export interface LoginAPIError {
loginAttemptCnt: number;
remainingTime: string;
accountStartDate: string;
}
// data
export type LoginAPIResponseData = LoginAPISuccess & LoginAPIError;
// response
export type LoginAPIResponse = APIResponse<LoginAPIResponseData>;
// params
export type LoginAPIFetchFn = LoginAPIParams & APIFnCallback<LoginAPIResponse>;
JS Doc 처럼 프로퍼티 설명도 추가할 수 있어 구조를 한 눈에 파악할 수 있는 장점도 있습니다.

리팩터링 및 유지보수 생산성 향상
any 타입은 타입 검사를 하지 않겠다 라는 의미를 가진 타입입니다. 그 만큼 TS를 쓴다고 해도 any로 추론되는 속성들이 많으면 런타임에 오류가 날 확률이 그 만큼 증가하게 됩니다.
any를 막기 위해 eslint 설정도 할 수 있지만 너무 과한 타입가드는 오히려 타입을 잡기 위해 더 많은 시간을 쏟아야 하는 경우도 있습니다.
미리 사전에 타입을 정의해두었다면 API 명세가 바뀌거나, 기존 FE 내부 코드에서 함수나 객체 등이 바뀐다 하여도 코드 작성 시에 타입 오류를 잡아내서 런타임 오류를 잡아 낼 수 있습니다.
코드 작성 시에 잡아 내지 못했다면 프로젝트 build 시에 컴파일 과정에서 tsc(typescript compiler)가 잡아내기 때문에 프로젝트가 더욱 더 안전성이 향상 됩니다.
인터페이스 타입 설계
최근 프론트엔드의 컴포넌트를 만드는 방식에는 여러 패턴들이 있습니다. 그 중에서 UI 관점에서 봤을 때 컴포넌트 설계 시에 작은 단위의 컴포넌트를 만들고 이들과 조합하여 또 하나의 UI를 만드는 식으로 아토믹 패턴과 유사한 방식으로 디자인 시스템을 구축하는 것이 특징입니다.
예를 들어 input, label을 구별하여 작성하고 inputForm이라는 label과 input이 합쳐진 컴포넌트를 작성하는 것입니다. 리액트에서는 각 컴포넌트 단위에서 필요한 props 들을 정의하고 이 컴포넌트를 가져다 쓰는 컴포넌트에서 하위 타입을 확장(extends)시켜 바텀 업 방식의 설계를 할 수 있습니다.
예시3)
input + label 타입 설계
// label/type.ts
export type LabelColor = 'light' | 'white';
export interface LabelProps {
id: string;
label: string;
className?: string;
color?: LabelColor;
}
// input/type.ts
import type { InputTypeHTMLAttribute } from 'vue';
import type { LabelProps } from '../label/type';
type InputFieldVariant = 'fill' | 'outline';
// input props
export interface Props {
// label
label: string;
type?: InputTypeHTMLAttribute;
// error
isError?: boolean;
errorMessage?: string;
variant?: InputFieldVariant;
// onchange
onInput?: (_e: Event) => void;
}
export interface PasswordIconProps {
show: boolean;
onVisible: () => void;
}
// label + input props
export type InputFieldProps = Omit<Props, 'variant'> &
Omit<LabelProps, 'color'> & {
showPassword?: PasswordIconProps;
labelColor?: LabelProps['color'];
inputFieldVariant?: Props['variant'];
};
// button/type.ts
export type ButtonColor = 'primary';
export type ButtonVariants = 'fill' | 'line';
export type ButtonSize = 'sm' | 'md' | 'lg';
export interface ButtonProps {
// color
color?: ButtonColor;
// variants
variants?: ButtonVariants;
// size
size?: ButtonSize;
onClick?: (_event: MouseEvent) => void;
disabled?: boolean;
className?: string;
}
// button.vue
<script setup lang="ts">
import type { ButtonProps } from './type';
const { color = 'primary', size = 'md', variants = 'fill', onClick, className, disabled } = defineProps<ButtonProps>();
const buttonPrefix = [size, `${variants}-${color}`].map((atr) => `button-${atr}`);
</script>
<template>
<button :disabled="disabled" :class="['button', className, ...buttonPrefix]" @click="onClick">
<slot />
</button>
</template>
<style lang="scss" scoped>
// default button styles
.button {}
// variants-color
.button-fill-primary {}
.button-line-primary {}
// size
.button-sm {}
.button-md {}
.button-lg {}
</style>
백엔드와의 타입 동기화
API code generator는 백엔드의 명세를 기반으로 프론트엔드의 타입스크립트 코드를 자동으로 생성해주는 툴입니다.
대표적으로는 swagger-typescript-api, open api generator 가 있습니다.
기존에는 API 명세를 보고 타이핑하면서 작업을 했었지만 이 툴을 도입하게 된다면 수작업을 제네레이터가 더 빠르고 더 정확하게 생성해주기 때문에 생산성이 향상 됩니다. 또한 개발 단계에서는 백엔드에서도 API 명세가 바뀌는 경우가 가끔 있는데 이 경우에도 프론트엔드에서 다시 생성하여 즉시 동기화할 수 있으므로 유지보수 효율도 높아집니다.

'TIL' 카테고리의 다른 글
| EC2 인스턴스 갑작스러운 다운, 2806번의 SSH 브루트포스 공격 (0) | 2025.12.14 |
|---|---|
| AWS S3를 활용한 프로필 이미지 업로드 구현하기 (0) | 2025.02.21 |
| swagger-typescript-api 도입 후 fetchUtils 마이그레이션 (0) | 2025.02.12 |
| DNS를 Route 53에서 Cloudflare로 이전하면서 디도스 방어하기 (0) | 2025.01.20 |
| 내 사이트 더 많이 노출 시키기(SEO) (0) | 2025.01.16 |