반응형
Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- CSS
- Visual Studio Code
- IntelliJ
- 문자열
- input
- SpringBoot
- ArrayList
- js
- javascript
- string
- 인텔리제이
- HashMap
- 배열
- vscode
- Array
- java테스트
- Eclipse
- 테스트자동화
- 자바
- math
- 자바스크립트
- 자바문법
- junit5
- list
- 단위테스트
- html
- junit
- 스프링부트
- 정규식
- Java
Archives
- Today
- Total
어제 오늘 내일
[TypeScript] 4. 타입 추론 — 언제 타입을 직접 쓰고 언제 맡길 것인가 본문
이 글의 목표: 모든 곳에 타입을 다는 건 초보의 실수입니다. TypeScript가 알아서 추론하는 곳과, 직접 명시해야 하는 곳을 구분합니다.
TypeScript는 대부분 스스로 안다
타입을 명시하지 않아도, 값을 보고 타입을 추론합니다.
let count = 10; // TypeScript는 count를 number로 추론
let title = "안녕"; // string으로 추론
let flag = true; // boolean으로 추론
// count = "열"; // ❌ 이미 number로 추론됐으므로 에러: number 같은 표기를 굳이 붙이지 않아도 됩니다. 오히려 붙이면 코드만 장황해집니다.
// 👎 불필요한 타입 표기
let count: number = 10;
// 👍 추론에 맡긴다
let count = 10;let vs const: 추론 결과가 다르다
let a = "hello"; // 타입: string (재할당 가능하므로 넓게 추론)
const b = "hello"; // 타입: "hello" (재할당 불가하므로 리터럴로 좁게 추론)const는 값이 바뀌지 않으므로, TypeScript가 리터럴 타입으로 좁혀 추론합니다. 이 차이는 나중에 유니온을 다룰 때 중요해집니다.
함수 반환 타입도 추론된다
// 반환 타입을 안 써도 number로 추론된다
function add(a: number, b: number) {
return a + b;
}
const result = add(1, 2); // result는 number다만 매개변수 타입은 추론되지 않습니다. 함수를 호출하는 쪽을 미리 알 수 없기 때문입니다.
// 👎 매개변수에 타입이 없으면 암묵적 any → strict 모드에서 에러
// function greet(name) { return `Hi ${name}`; }
// 👍 매개변수는 직접 명시해야 한다
function greet(name: string) {
return `Hi ${name}`;
}그럼 반환 타입은 써야 할까, 말아야 할까?
이건 취향과 상황이 갈립니다. 저는 공개 API(라이브러리, 모듈 경계)에는 명시하고, 내부 헬퍼 함수는 추론에 맡기는 편을 권합니다.
// 👍 명시하면, 실수로 잘못된 값을 반환할 때 함수 안에서 바로 잡힌다
function calculateTax(price: number): number {
// 만약 실수로 문자열을 반환하면 이 함수 내부에서 에러 발생
return price * 0.1;
}반환 타입을 명시하면, 버그가 "함수를 호출하는 곳"이 아니라 "함수 정의"에서 잡힙니다.
언제 타입을 직접 써야 하나
1) 매개변수 (항상)
function format(value: number, digits: number) { /* ... */ }2) 값 없이 선언만 할 때
let selectedId: number | null = null; // 지금은 null이지만 나중에 number가 들어온다
// 이때 타입을 안 쓰면 null 타입으로 굳어져 number를 못 넣는다3) 추론이 너무 넓거나 좁을 때
// 추론에 맡기면 string[]으로 넓어진다
const roles = ["admin", "user"]; // string[]
// as const 를 붙이면 값이 바뀌지 않는 "고정된 배열"로 취급되어,
// 원소도 넓은 string이 아니라 리터럴 그대로 유지된다
const roles2 = ["admin", "user"] as const; // readonly ["admin", "user"]
// 위 배열로부터 "admin" | "user" 유니온 타입을 뽑아낸다
type Role = typeof roles2[number]; // "admin" | "user"마지막 줄 typeof roles2[number]가 처음엔 낯설 텐데, 두 조각으로 나눠 보면 쉽습니다.
typeof roles2: 변수roles2의 타입을 가져옵니다. 즉readonly ["admin", "user"]. (typeof를 타입 자리에서 쓰면 "이 값의 타입"이라는 뜻입니다.)[number]: 배열 타입에number로 인덱스 접근을 하면 "모든 위치의 원소 타입"을 유니온으로 꺼냅니다.["admin", "user"]의 0번, 1번 원소 타입을 합쳐"admin" | "user"가 됩니다.
정리하면 "roles2 배열의 원소들이 가질 수 있는 값의 유니온"을 자동으로 만든 것입니다. 이렇게 하면 배열(값)과 타입이 항상 한 곳에서 관리되어, roles2에 새 역할을 추가하면 Role 타입도 자동으로 따라옵니다.
// 예: 배열에 "guest"를 추가하면
const roles2 = ["admin", "user", "guest"] as const;
type Role = typeof roles2[number]; // "admin" | "user" | "guest" 로 자동 확장4) 빈 배열/객체로 시작할 때
// 👎 빈 배열은 any[]로 추론되어 안전성을 잃는다
const items = []; // any[]
items.push(1);
items.push("문자열"); // 막지 못함
// 👍 무엇을 담을지 명시한다
const nums: number[] = [];
nums.push(1);
// nums.push("x"); // ❌ 이제 막힌다원칙: "값이 있으면 맡기고, 값이 없거나 넓으면 명시하라"
// 값이 명확 → 추론에 맡긴다
const total = 1000;
const user = { id: 1, name: "철수" };
// 값이 아직 없거나(변수만 선언), 추론이 원하는 것보다 넓을 때 → 명시
let response: User | null = null;
const config: Record<string, boolean> = {};정리
- TypeScript는 대부분의 지역 변수와 반환 타입을 알아서 추론한다. 불필요한 타입 표기는 오히려 코드를 지저분하게 만든다.
- 매개변수는 항상 명시한다.
- 값 없이 선언만 하거나, 빈 배열/객체로 시작하거나, 추론이 너무 넓을 때는 명시한다.
- 공개 함수의 반환 타입을 명시하면 버그를 정의 자리에서 잡을 수 있다.
여기까지가 Part 1입니다. 다음 글부터 타입 시스템의 핵심으로 들어갑니다.
반응형
'IT > TypeScript' 카테고리의 다른 글
| [TypeScript] 6. 유니온과 인터섹션, 그리고 리터럴 타입 (0) | 2026.08.10 |
|---|---|
| [TypeScript] 5. interface vs type — 무엇을 언제 쓸까 (0) | 2026.08.09 |
| [TypeScript] 3. 기본 타입 총정리 (0) | 2026.08.09 |
| [TypeScript] 2. 개발 환경 세팅 (tsc, tsx, 에디터 설정) (0) | 2026.08.09 |
| [TypeScript] 1. JavaScript의 한계와 정적 타입이 해결하는 문제 (0) | 2026.08.09 |
Comments