TypeScript 도입 전후, 뭐가 달라졌나
JavaScript만 쓰던 시절
프리랜서 개발을 시작하던 2021년, 나는 JavaScript만 사용했다. React, Node.js 전부 JS로 작성했다. "TypeScript는 대기업에서나 쓰는 거 아닌가?", "타입 달아야 하니까 느려지지 않을까?" 같은 생각이 있었다.
그러다 한 프로젝트에서 충격적인 일을 겪었다.
계기: 런타임 에러의 악몽
당시 진행하던 프로젝트에서 API 응답 구조가 바뀌었다. 백엔드 개발자가 user.profile.name을 user.profileData.userName으로 변경했는데, 프론트엔드의 20곳 이상에서 이 데이터를 사용하고 있었다.
배포 후 발견한 버그였다.
// 이전 코드
const displayName = user.profile.name
// TypeError: Cannot read property 'name' of undefined
사용자들이 직접 보고할 때까지 몰랐다. 수동으로 모든 파일을 찾아다니며 수정하는 데 반나절이 걸렸다. 같은 실수를 두 번 할 수는 없었다.
첫 TypeScript 프로젝트
다음 프로젝트부터 TypeScript를 도입했다. 처음에는 정말 답답했다. 에러가 빨간색으로 쏟아지고, 뭘 어떻게 고쳐야 하는지 감이 안 잡혔다.
// 처음 마주친 에러들
// Object is possibly 'undefined'.
// Property 'xxx' does not exist on type 'yyy'.
// Type 'string' is not assignable to type 'number'.
하지만 2주쯤 지나니까 적응됐다. 그리고 깨달았다.
"이 에러들은 원래 런타임에서 터졌을 버그들이다."
진짜 달라진 것들
1. API 응답 타입 정의
이제 API 응답은 반드시 타입으로 정의한다.
interface User {
id: string
profileData: {
userName: string
email: string
avatar?: string
}
}
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
백엔드에서 응답 구조가 바뀌면? 타입만 수정하면 된다. 그러면 TypeScript가 영향받는 모든 코드를 알려준다. 빨간 줄 뜬 곳만 가서 고치면 끝.
2. 자동완성의 힘
VS Code에서 객체를 타이핑하면 자동완성이 된다. user.을 치면 profileData, id 등 가능한 속성이 나온다. 문서를 뒤지거나 console.log로 확인할 필요가 없다.
// 자동완성이 없을 때
const name = user.??? // profile? name? userName?
// 타입이 있을 때
const name = user.profileData.userName // Cmd+Space로 바로 선택
3. 리팩토링 자신감
예전에는 함수 이름을 바꾸거나 구조를 변경할 때 겁이 났다. "어디서 쓰이는지 다 찾았을까?"라는 불안감.
TypeScript에서는 F2 (Rename Symbol) 한 번이면 프로젝트 전체에서 안전하게 이름이 바뀐다. 참조가 누락되면 빌드가 실패하니까 확신을 가지고 수정할 수 있다.
4. 실수 방지
가장 좋아하는 부분이다.
// 이런 실수를 컴파일 타임에 잡아준다
function calculateTotal(price: number, quantity: number) {
return price * quantity
}
// Error: Argument of type 'string' is not assignable to parameter of type 'number'
calculateTotal("100", 2)
// 이런 것도
const items: Product[] = []
items.push({ id: 1, name: "item" }) // Error: 'price' is required
생산성 변화
처음 2주: 생산성 50% 하락
타입 에러와 싸우느라 코드 작성 속도가 반으로 줄었다. any를 남발하고 싶은 유혹과 싸워야 했다.
1개월 후: 원래 수준 회복
타입 정의가 익숙해지고, 자주 쓰는 패턴들이 손에 익었다. 에디터 자동완성 덕에 코드 작성 속도는 비슷해졌다.
3개월 후: 생산성 향상 체감
진정한 이득은 유지보수 시간 감소였다.
- 런타임 버그 찾는 시간: 대폭 감소
- API 변경 대응 시간: 몇 시간 → 몇 분
- 코드 리뷰 시 타입 오류 발견: 거의 없음
- 6개월 전 코드 수정: 타입 보고 바로 이해 가능
외주 프로젝트에서의 TypeScript
혼자 개발할 때와 달리, 외주 프로젝트에서는 몇 가지 더 고려할 점이 있다.
인수인계
프로젝트를 클라이언트에게 넘기거나 다른 개발자가 유지보수할 때, TypeScript 코드가 훨씬 이해하기 쉽다. 타입 정의 자체가 문서 역할을 한다.
// 이 함수가 뭘 받고 뭘 리턴하는지 명확하다
function processOrder(
order: Order,
options: ProcessOptions
): Promise<ProcessResult> {
// ...
}
점진적 마이그레이션
레거시 JavaScript 프로젝트를 맡았을 때, 한 번에 다 바꾸지 않아도 된다. tsconfig에서 allowJs: true를 설정하면 JS와 TS가 공존할 수 있다.
새로 작성하는 파일만 .ts로 만들고, 시간 날 때 기존 파일들을 하나씩 전환하면 된다.
개발 환경 추천
TypeScript 경험을 최대로 끌어올리려면:
- VS Code - TypeScript 지원이 가장 좋다
- strict 모드 - 처음엔 힘들지만 켜두는 게 좋다
- ESLint + typescript-eslint - 타입 관련 린트 규칙 활용
- Prisma - DB 스키마에서 타입 자동 생성
// tsconfig.json 권장 설정
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitAny": true
}
}
any는 언제 쓰나
가끔 any가 필요한 상황이 있다.
- 외부 라이브러리의 타입이 잘못됐을 때
- 정말 동적인 데이터를 다룰 때
- 빠른 프로토타이핑 (나중에 반드시 수정)
하지만 any를 쓸 때는 주석으로 이유를 남긴다.
// TODO: API 응답 타입 확정되면 수정
const data: any = await fetchUnknownAPI()
결론
TypeScript 도입 전후, 가장 큰 변화는 "불안함에서 확신으로"다.
예전에는 코드를 수정할 때마다 "이거 다른 데서 문제 안 생기겠지?"라는 불안함이 있었다. 지금은 빌드가 성공하면 적어도 타입 관련 버그는 없다는 확신이 있다.
초기 학습 비용이 있지만, 장기적으로는 분명히 이득이다. 특히 혼자 개발하는 사람에게 TypeScript는 동료 개발자 한 명이 코드 리뷰를 해주는 것과 비슷한 효과가 있다.
아직 JavaScript만 쓰고 있다면, 다음 프로젝트에서 한번 시도해보길 권한다. 처음 2주만 참으면 된다.