타입 추론 (Type Inference)

Changhan·2025년 1월 21일

Typescript

목록 보기
5/29

타입 추론이란, 타입을 굳이 입력하지 않아도 타입스크립트 자체에서 변수나 함수, 파라미터 등의 타입을 추론하는 것이다.

예제 코드를 보자.

let stringType = 'string';
let booleanType = true;
let numberType = 30;

해당 변수들에 마우스를 오버하면 우리가 각 변수들에 타입을 주지 않았는데도 타입스크립트가 알아서 추론하여 각각의 변수의 타입을 string, boolean, number로 지정해준 것을 확인할 수 있다. 이것이 타입 추론이다.


booleanType = false;
booleanType = 'false'; // 문자열이므로 에러

당연한 얘기지만 불리언 타입에 다른 불리언 타입을 할당하는 것은 문제가 없다. 하지만 불리언 타입이 아닌 다른 타입을 할당하려고 하면 에러가 발생할 것이다.


다음 예제를 살펴보자.
const string = 'const string';

const 키워드로 변수를 선언을 했더니 const string이라고 지정되었다. 이는 string 타입 중에서도 const string이라는 글자만 입력할 수가 있다는 의미다. 이것을 string literal 타입이라고 한다.

이게 가능한 이유는 const 키워드로 선언한 변수는 이후에 값을 변경하지 못하기 때문이다.


string 값만 할 수 있는 것은 아니다. 다른 타입도 다 가능하다.
const booleanType = true;
const constNumberType = 123;
const constUndefinedType = undefined;
const constNullType= null;

각각의 변수의 타입이 true, 123, undefined, null이 된다. 그래서 const로 선언한 변수의 타입같은 경우에는 좀 더 구체적인 타입으로 추론을 한다고 볼 수 있다.


객체는 어떤지 살펴보자.

let taeYeon = {
  name: '태연',
  year: 1989,
}
const taeYeon2 = {
  name: '태연',
  year: 1989,
}

const로 선언된 taeYeon2는 여전히 name의 타입이 string, year의 타입이 number인 것을 볼 수 있다. 더 구체적으로 선언이 되지 않았다.
이 말은 즉슨, taeYeon2.name = '윤아' 이런 식으로 값을 바꿀 수 있다는 것이다.

그럼 객체에서 각 프로퍼티가 조금 더 구체적인 타입을 가지게 하려면 어떻게 해야 할까?

const taeYeon2 = {
  name: '태연' as const, // '태연'이라는 값을 const로 캐스팅하라는 의미
  year: 1989 as const, // '1989'라는 값을 const로 캐스팅하라는 의미
}

이제 name의 타입은 태연, year의 타입은 1989라는 구체적인 타입을 가지게 되었다. 그렇게 되면 우리가 taeYeon2의 프로퍼티인 nameyear을 변경할 수 없게 된다.

taeYeon2.name = '윤아';
taeYeon2.year = 1990;

추후에 taeYeon2 객체를 사용할 때 프로퍼티를 불러오면 각 프로퍼티의 타입을 추론받을 수 있다. 그래서 데이터 타입의 흐름을 정확하게 파악할 수 있다.


배열에 대해서도 살펴보자.

let numbers = [1, 2, 3, 4, 5]; // type: number[]
let numbersAndString = [1, 2, 3, '4', '5']; // type: (string | number)[]

numbers의 타입은 number[]로 추론할 것이다. 중요한 것은 numbersAndString의 타입을 보자. (string | number)[]라고 추론이 된다. 이것의 의미는 string 또는 number로 이루어진 배열이라는 의미다.

여기서 값을 추가해보자.

numbers.push(6); // O
numbers.push('6'); // X

numbersAndString.push(10); // O
numbersAndString.push('10'); // O

numbers는 number 타입으로만 구성된 리스트여야 하기 때문에 number 타입이 아닌 다른 타입을 추가할 수 없는 것을 볼 수 있다.
하지만 numbersAndString은 number 또는 string가 아닌 타입을 넣을 수 없다.


const number = numbers[0]; // number
const nas = numberAndString[0]; // number | string
const nas2 = numberAndString[100]; // number | string

nas의 타입은 number 타입일 것 같지만 그렇지 않다. number | string이다. 타입스크립트는 거기까기 생각하지 못한다.

추가적으로, 배열의 길이보다 긴 인덱스를 가져오려고 하면 number | string으로 추론을 해버린다. 이는 버그가 발생할 수 있는 요소다.

만약 이 배열도 구체적인 값으로 구성이 된 타입으로 선언해보고 싶다면?

const numbers2 = [1, 3] as const; // readonly [1, 3]

위에서 했다 시피 const로 캐스팅을 해주면 된다. 위의 코드를 튜플이라고 부른다. 자세한 건 뒤에서 배워보자.

numbers2.push(100); // X
numbers2[0] = 23; // X

배열도 캐스팅을 해주면 값을 넣을 수도 수정할 수도 없게 되는 건 마찬가지다.

const first = numbers2[0]; // type: 1
const first2 = numbers[100]; // X

numbers2[0]의 값은 1이기 때문에 first의 타입은 그대로 1이 된다.
이것은 위에서 numberAndString에서 값을 가져왔을 때의 타입과 완전히 다른 것을 볼 수 있다. 위에서는 string | number 타입이었는데 여기서는 1이 되었다.

그 이유는,
튜플을 선언하게 되면 정확히 몇 번째에 어떤 값이 들어있어야 하는지 알 수 있기 때문에 값을 추론할 때도 정확한 값을 가져온다.
그리고 튜플은 일반 배열과는 다르게 배열의 길이보다 큰 인덱스를 가져오면 에러가 발생한다.


다음 포스팅에서는 이번에 맛만 본 캐스팅에 대해서 좀 더 알아보자.

후기

첫 번째로 string, number 타입 같은 것들이 있는데 캐스팅을 하는 이유에 대한 것이다. 굳이 더 구체적인 값으로 타입을 정해줘야 하는 이유나 상황이 있을까? 하는 생각이 들었다. 아직 캐스팅에 대해 배우지 못해서 그런 걸 수도 있다는 생각이 든다

0개의 댓글