숏컷 코드
let username: string = "refdock";
const port = 3000;
function repeat(text: string, times: number): string {
return text.repeat(times);
}추론 경계
타입 주석과 추론에서 먼저 나눠 볼 기본형은 아래입니다.
- 변수 주석:
let name: string = "Mina" - 함수 인자 주석:
function f(name: string) - 반환 타입 주석:
function f(): string - 추론:
const port = 3000 - 애매한 초기값 명시:
const items: string[] = [] - 객체 배열/맵 초기값 명시:
const byId: Record<string, User> = {}
1) 함수 경계는 타입 주석을 먼저 본다
함수 인자와 반환 타입은 다른 코드가 이 함수를 어떻게 써야 하는지 보여 주는 계약입니다.
function connect(host: string, port: number): string {
return `${host}:${port}`;
}특히 export하는 함수나 공용 유틸은 호출부가 멀리 떨어져 있을 수 있으므로, 경계 타입을 명시하는 편이 읽기 쉽습니다.
2) 지역 변수는 초기값이 충분하면 추론에 맡긴다
초기값이 명확한 지역 변수는 TypeScript가 잘 추론합니다. 이 경우 타입을 다시 적으면 오히려 반복만 늘어날 수 있습니다.
const port = 3000;
const isReady = true;
const tags = ["ts"];즉 "추론이 잘 되는 곳에서 굳이 반복하지 않는다"가 기본 감각입니다.
3) 애매한 시작값은 명시로 계약을 고정한다
빈 배열, null, 빈 객체처럼 초기값만 봐서는 의도가 충분히 드러나지 않는 경우에는 타입을 직접 적는 편이 안전합니다.
const items: string[] = [];
let selectedId: string | null = null;
const byId: Record<string, { id: string }> = {};이런 값은 나중에 어떤 타입으로 자라나야 하는지가 중요하므로, 초반에 계약을 잡아 두는 편이 낫습니다.
const names = [];
names.push("mina");
const explicitNames: string[] = [];
explicitNames.push("mina");빈 배열처럼 출발점이 흐린 값은 명시가 없으면 의도보다 넓거나 애매한 타입으로 읽히기 쉽습니다.
const names = [];
names.push("mina");
names.push(1); // 의도보다 넓게 흘러가기 쉽다
const explicitNames: string[] = [];
explicitNames.push("mina");
// explicitNames.push(1); // 타입 오류즉 추론이 편하다고 해서 항상 안전한 것은 아닙니다. 초기값이 너무 비어 있으면 추론 결과도 흐려진다는 점을 같이 봐야 합니다.
4) 복잡한 반환 타입은 필요할 때만 명시한다
모든 반환 타입을 기계적으로 적을 필요는 없지만, 함수가 공개 API이거나 반환 shape가 중요한 경우에는 명시가 도움이 됩니다. 반대로 내부 헬퍼에서 식이 단순하면 추론에 맡겨도 충분한 경우가 많습니다.
5) "많이 적기"보다 "어디에 적을지"가 더 중요하다
TypeScript는 문법 양을 늘리는 도구가 아니라, 설계 경계를 드러내는 도구로 쓰는 편이 좋습니다. 그래서 지역 변수보다 함수 경계, 구현 내부보다 공개 계약이 우선입니다.
언제 직접 적을까
타입 주석을 붙일 때 볼 점
- 함수 인자다: 타입 주석 명시
- 공개 반환값이다: 필요 시 명시
- 초기값이 분명한 지역 변수다: 추론에 맡김
- 빈 배열이나
null시작값이다: 명시적 타입 추가 - 외부에 노출되는 계약이다: 더 의식적으로 적음
주의할 점
추론이 잘 된다고 해서 함수 경계까지 다 생략하면 계약이 흐려집니다. 특히 export 함수는 인자와 반환 타입을 더 의식적으로 적는 편이 좋습니다.
// ❌ 호출 계약이 바로 안 보임
export function parse(input) {
return JSON.parse(input);
}
// ✅ 함수 경계를 명확히 드러냄
export function parse(input: string): unknown {
return JSON.parse(input);
}
// ❌ null 시작값이 의도를 충분히 못 보여 줌
let selectedId = null;
// ✅ 어떤 값으로 자라날지 계약을 고정
let selectedId: string | null = null;// ❌ 지역 변수라고 무조건 타입을 생략하면 흐려질 수 있다
let result;
result = 1;
result = "done";
// ✅ 자라날 타입이 정해져 있으면 먼저 고정한다
let resultText: string | null = null;
resultText = "done";참고 링크
1 sources