목차
2. 타입 추론의 기본
3. 가장 적절한 타입 (The Best Common Type)
4. 문맥 상의 타이핑(Contextual Typing)
5. 타입을 명시적으로 선언해야하는 이유
6. 타입 추론 오류와 해결 방법
- 초기화가 없는 경우의 타입 추론 오류 (암묵적 any)
- 유니온 타입에서의 잘못된 추론
- 함수 반환 타입 추론 오류
- 객체 속성 타입 추론 오류
타입 추론에 대해서 알아보자!
1. "타입 추론"이란?
* 타입 추론: 타입스크립트가 코드에서 명시적으로 타입을 선언하지 않아도, 코드의 문맥과 값을 바탕으로 변수, 함수, 표현식 등의 타입을 자동으로 추론하는 것을 말한다.
타입스크립트는 이러한 추론을 통해 코드의 타입 안정성을 유지하면서도, 개발자가 모든 타입을 명시적으로 지정할 필요 없이 편리하게 코드를 작성할 수 있게 해준다.
2. 타입 추론의 기본
let x = 3;
- 변수를 선언하거나 초기화 할 때 타입이 추론된다.
- 위의 예시에서는 x에 대한 타입을 따로 지정하지 않더라고 일단 x는 number라고 간주된다.
- 이외에도 변수, 속성, 인자의 기본값, 함수의 반환값 등을 설정할 때 타입추론이 일어난다.
3. 가장 적절한 타입 (Best Common Type)
* Best Common Type: 타입스크립트에서 타입을 추론할 때, 여러 값이 있을 경우 그 값들을 모두 포함할 수 있는 가장 적절한 공통 타입을 선택하는 방식이다. 타입스크립트는 각 요소의 타입을 보고 공통된 상위 타입을 찾는다.
예시 1 )
let items = [0, "hello"];
// Best Common Type은 (string | number)[]
- 위의 예시에서 items는 숫자(0)와 문자열("hello")을 포함한 배열이다.
- 타입스크립트는 이 배열의 타입을 추론할 때, 각 요소의 타입을 보고 모든 요소를 포함할 수 있는 가장 공통적인 타입을 찾아낸다.
- 이 경우, 0은 number 타입이고 "hello"는 string 타입이다.
- 그래서 타입스크립트는 items의 타입을 (string | number)[]로 추론한다. 이게 바로 Best Common Type을 찾는 과정이다.
예시 2 )
let arr = [0,1,null]
// Best Common Type은 (number | null)[]
- 위의 예시에서 arr의 타입을 추론하기 위해서 각 아이템을 살펴본다.
- 아이템은 크게 number(0과 1) 과 null 로 구분된다.
- 타입스크립트는 이것을 포함하는 가장 적절한 공통 타입을 찾는다.
- 타입스크립트는 이 세 가지 요소를 고려해 arr의 타입을 (number | null)[]로 추론한다.
- 즉, 이 배열은 number 타입과 null 타입을 모두 포함할 수 있는 공통 타입을 결정하고, 이 공통 타입이 배열의 타입이 된다.
예시 3 )
class Animal {
move() {}
}
class Dog extends Animal {
bark() {}
}
class Bird extends Animal {
fly() {}
}
let animals = [new Dog(), new Bird()];
// Best Common Type은 Animal[]
- 여기서 animals 배열은 Dog와 Bird의 객체를 포함하고 있다.
- 타입스크립트는 Dog와 Bird 클래스가 둘 다 Animal을 상속받고 있으므로, 가장 공통적인 타입은 Animal이라고 추론하여 animals의 타입을 Animal[]로 설정한다.
4. 문맥상의 타이핑 (Contextual Typing)
* Contextual Typing: 타입스크립트에서 변수나 표현식의 타입이 해당 변수가 위치한 문맥에 의해 추론되는 방식을 의미한다.
이는 명시적으로 타입을 지정하지 않아도, 타입스크립트가 주변 코드의 타입 정보를 이용해 적절한 타입을 추론해준다.
Contextual Typing은 일반적으로 다음과 같은 상황에서 발생한다.
- 이벤트 핸들러 (예: onClick, onChange)
- 배열 메서드 (예: map, forEach, filter)
- 함수의 인자가 특정 타입을 요구할 때
예시 1 ) mouseEvent
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- OK
console.log(mouseEvent.kangaroo); //<- Error!
};
- 타입스크립트 검사기는 window.onmousedown 에 할당되는 오른쪽 함수의 타입을 추론하기 위해, window.onmousedown 타입을 검사한다.
- 타입 검사가 끝나면, 함수의 타입이 마우스 이벤트와 연관 있다고 추론한다.
- mouseEvent 인자에 button 속성은 있지만, kangaroo는 없다고 결론을 내린다.
예시 2 ) uiEvent
window.onscroll = function(uiEvent) {
console.log(uiEvent.button); //<- Error!
}
- 타입스크립트 검사기는 window.onscroll에 할당되는 오른쪽 함수 타입을 추론하기 위해, window.onscroll 타입을 검사한다.
- 타입 검사가 끝나면, 함수의 타입이 ui 이벤트와 연관 있다고 추론한다.
- uiEvent 인자에 button 속성은 없다고 추론한다.
const handler = function(uiEvent) {
console.log(uiEvent.button); //<- OK
}
위 코드는 오른쪽 함수 표현식이 앞의 예제와 동일하지만 함수가 할당되는 변수만으로는 타입을 추정하기 어렵기 때문에 아무 에러가 나지 않는다.
예시 3 ) 배열 메서드에서 문맥상의 타이핑
let numbers = [1, 2, 3];
numbers.forEach(num => {
console.log(num); // 'num'의 타입이 number로 추론됨
});
numbers 배열의 요소가 모두 number 타입이므로, forEach 콜백 함수의 매개변수 num 역시 문맥을 통해 타입스크립트가 number 타입으로 추론한다.
5. 타입을 명시적으로 선언해야하는 이유
TypeScript의 타입 추론 기능은 매우 강력해서, 많은 경우 개발자가 타입을 명시하지 않아도 올바르게 타입을 추론해준다. 하지만 타입을 명시적으로 선언하는 것이 중요한 이유는 여러 가지가 있다.
1) 코드의 명확성과 가독성
명시적으로 타입을 선언하면, 다른 개발자나 미래의 자신이 코드를 읽을 때 어떤 타입의 데이터가 사용되는지 더 쉽게 이해할 수 있다. 타입 선언은 코드의 의도를 명확하게 드러내준다.
const result: number = add(10, 20); // 명시적으로 결과 타입을 알 수 있음
2) 복잡한 타입 관리
함수의 매개변수나 리턴 타입이 복잡한 경우, 명시적인 타입 선언이 타입 추론보다 더 정확하고 안전한 코드를 작성하는 데 유리하다. 특히 객체, 배열, 유니온 타입 같은 복잡한 구조에서는 명시적으로 타입을 지정하는 것이 추후 유지보수에 좋다.
const user: { name: string; age: number } = { name: "Alice", age: 25 };
3) 예상치 못한 타입 추론 방지
타입 추론이 올바르게 이루어지는 경우, 개발자가 타입을 명시하지 않아도 컴파일러가 자동으로 타입을 추론하기 때문에 에러가 발생하지 않을 수 있다.
타입 추론이 잘 되는 경우 예시
let message = "Hello, world!"; // TypeScript가 자동으로 message를 string으로 추론
message = "Hi!"; // 정상적으로 동작, 타입 오류 없음
하지만 타입 추론이 항상 개발자의 의도와 맞지 않을 수 있다. 예를 들어, 변수 초기화가 늦춰지면 컴파일러가 any 타입으로 추론할 수 있어 타입 안전성이 저하될 수 있다. 아래에서 그 예시를 살펴볼 수 있다.
6. 타입 추론 오류와 해결 방법
1) 초기화가 없는 경우의 타입 추론 오류 (암묵적 any)
let value; // TypeScript가 자동으로 any 타입으로 추론
value = 123; // 처음에는 number로 사용
value = "Hello"; // 나중에 string으로 변경해도 에러 발생 안함 (any이기 때문)
console.log(value.toUpperCase()); // 런타임 시 string일 때는 동작하지만, number일 때는 에러 발생 가능
위 코드에서 value는 초기값이 없으므로 any 타입으로 추론된다.
이후 number와 string 값을 혼용해서 사용할 수 있는데, 이는 의도하지 않은 결과를 초래할 수 있다.
특히 number일 때는 toUpperCase() 메서드를 사용할 수 없어 런타임 오류가 발생합니다.
TypeScript의 장점은 타입 안전성인데, 타입을 명시하지 않으면 예기치 않은 타입 에러가 발생할 수 있다.
특히 any 타입으로 추론될 때, 오류를 잡기 어려운 경우가 생길 수 있는 것이다.
따라서 명시적인 타입 선언을 통해 이런 문제를 미리 방지할 수 있게 된다.
let value: number | string; // value는 number 또는 string 타입
value = 123; // number 할당
value = "Hello"; // string로 다시 할당
console.log(value.toUpperCase()); // 컴파일 오류가 발생하지 않음
하지만 만약 value가 number일 경우, toUpperCase() 메서드는 존재하지 않기 때문에 런타임 오류가 발생할 수도 있기 때문에,
이 문제를 방지하려면 타입 가드를 사용하여 value의 타입을 체크한 후 메서드를 호출해야 한다.
if (typeof value === "string") {
console.log(value.toUpperCase()); // 안전하게 호출 가능 (타입 안정성 보장됨)
} else {
console.log("value is a number:", value); // number일 경우 처리
}
2) 유니온 타입에서의 잘못된 추론
TypeScript는 유니온 타입을 사용할 때 상황에 따라 타입을 좁혀 추론하지만, 모든 경우에 정확하게 타입을 추론하지 못할 때가 있다.
function getLength(something: string | number): number {
return something.length; // 오류 발생! number에는 length 속성이 없음
}
위 코드에서 something은 string 또는 number일 수 있다. TypeScript는 이를 자동으로 추론하지만, number 타입에는 length 속성이 없기 때문에 오류가 발생할 수 있다.
따라서 이 경우에는 타입 가드를 사용해 타입을 명확히 구분해야 한다.
function getLength(something: string | number): number {
if (typeof something === 'string') {
return something.length;
} else {
return something.toString().length; // number일 경우 문자열로 변환 후 길이 계산
}
}
3) 함수 반환 타입 추론 오류
TypeScript는 함수의 반환 타입도 추론한다. 하지만 복잡한 경우 명확한 타입 선언 없이 자동 추론에 의존하면 의도와 다른 결과가 나올 수 있다.
function add(a: number, b: number) {
if (a > b) {
return a - b; // number 반환
}
return "b is greater"; // string 반환
}
const result = add(5, 10); // result 타입은 string | number가 됨
console.log(result.toUpperCase()); // 런타임 오류! number에는 toUpperCase가 없음
위 예시에서 add 함수는 number 또는 string을 반환할 수 있지만, 타입 추론에 의존하면 반환 타입이 string | number로 추론된다.
이후 result를 사용할 때, result가 string이 아닐 수도 있다고 판단하기 때문에, 이 코드에서 타입 안정성을 보장하지 않는다.
TypeScript는 result가 string 또는 number일 수 있으므로, toUpperCase()를 호출하면, 타입 검사에서 에러를 발생시키는 것이다.
따라서 이 경우에도 타입 가드를 사용해 타입을 명확히 구분해야 한다.
if (typeof result === "string") {
console.log(result.toUpperCase()); // 안전하게 호출 가능
} else {
console.log(result); // number일 경우의 처리
}
이렇게 하면 result가 string일 때만 toUpperCase()를 호출하므로 타입 안전성이 보장되고 런타임 오류를 방지할 수 있다.
4) 객체 속성 타입 추론 오류
객체에서 속성을 조건에 따라 동적으로 할당할 때도 잘못된 타입 추론이 발생할 수 있다.
const person = {
name: "Alice",
age: 25,
};
person.job = "Developer"; // 오류 발생, TypeScript는 초기 객체 구조만을 기준으로 타입을 추론했기 때문
위 예시에서 TypeScript는 person 객체가 name과 age 속성만 가진다고 추론하지만, 나중에 job 속성을 추가하려고 하면 타입 오류가 발생한다. 이는 객체 리터럴의 구조가 고정되어 있다고 판단하기 때문에 발생한다.
따라서 선택적 속성을 사용하여 객체의 타입을 확장 가능하게 명시적으로 정의해야한다.
const person: { name: string; age: number; job?: string } = {
name: "Alice",
age: 25,
};
// 선택적 속성이므로 오류 없음
person.job = "Developer"; // 정상적으로 동작
'Languages > TypeScript' 카테고리의 다른 글
[TypeScript] TypeScript 컴파일러(tsc)의 컴파일 과정 (3) | 2024.12.09 |
---|---|
[TypeScript] 타입 호환 (Type Compatibility) (11) | 2024.10.15 |
[TypeScript] 타입 가드 (Type Guard) (0) | 2024.09.30 |
[TypeScript] 타입 단언 (Type Assertion) (0) | 2024.09.30 |
[TypeScript] 타입 건전성 (Soundness ) (0) | 2024.09.29 |