본문 바로가기

[TS] 타입 별칭 / 타입 추론 / 타입 단언

[TS] 타입 별칭 / 타입 추론 / 타입 단언

타입 별칭(Type Aliases)

 

타입 별칭(Type Aliases)은 타입의 새로운 이름을 만드는 것이다.

  • 이는 새로운 이름으로 기존의 타입을 참조하는 것을 의미한다.
  • 타입 별칭을 이용하여 타입의 새로운 이름을 만들 때 키워드 type을 사용하여 작성한다. 
  • JavaScript에서는 타입 별칭을 따로 지원하지 않으며, TypeScript에서만 지원된다. 

 

아래의 코드를 보자.

  • 원래 string이라는 타입이 존재하고 있었다.
  • 이에 myString이라는 새로운 이름을 부여했다.
  • 여기서 myString과 string은 동일한 의미를 갖게 된다.
  • 즉, 타입을 정의할 수 있는 모든 곳에는 타입 별칭을 쓸 수 있다.
type MyString = string;

let str1: string = 'hello!';

// string 타입처럼 사용할 수 있다.
let str2: MyString = 'hello world!';

 

이런 방식으로 타입 별칭을 사용하면 코드를 더 간결하고 가독성 좋게 만들 수 있다.

  • 또한 복잡한 타입을 간략하게 표현하고, 타입 정의를 재사용하는 등 가독성을 높일 수 있다.

 

아래의 예시를 보자.

type Person = {
  id: number;
  name: string;
  email: string;
}

//Commentary 인터페이스에서 Person 타입을 참조하고 있다.
interface Commentary {
  id: number;
  content: string;
  user: Person;
}

//객체에서 Commentary 인터페이스를 참조하고 있다.
let comment1: Commentary = {
    id: 1,
    content: "뭐예요?",
    user: {
        id: 1,
        name: "김코딩",
        email: "kimcoding@codestates.com",
    },
}

//Commentary 인터페이스 내부에 content 프로퍼티가 존재하기 때문에 
//content 프로퍼티를 작성하지 않으면 컴파일 에러가 난다.
let kimcoding: Commentary = {
    id: 1,
    user: {
        id: 1,
        name: "김코딩",
        email: "kimcoding@codestates.com",
    },
};

//Person 타입 내부에 isDeveloper 프로퍼티가 존재하지 않기 때문에 
//isDeveloper 프로퍼티를 작성할 시 컴파일 에러가 난다.
let kimcoding: Commentary = {
    id: 1,
    content: "뭐예요?",
    user: {
        id: 1,
        name: "김코딩",
        email: "kimcoding@codestates.com",
        isDeveloper: true,
    },
};

 

이처럼 인터페이스나 다른 변수를 정의할 때 타입 별칭으로 정의한 타입을 참조하게 됨으로써 코드를 더 간결하고 가독성 좋게 만들 수 있다.

  • 타입 별칭으로 만들어진 타입을 참조할 시에는 인터페이스와 마찬가지로 내부에 정의된 프로퍼티를 전부 참조해야만 한다.
  • 또한 타입 별칭으로 만들어진 타입 내부에 정의된 프로퍼티 외에 다른 프로퍼티를 더 작성하게 되면 그 또한 컴파일 에러가 난다.

 

인터페이스 vs 타입 별칭

타입 별칭 또한 인터페이스와 같은 특징이 있기 때문에, 인터페이스의 역할을 타입 별칭이 수행할 수도 있다. 그러나 인터페이스와 타입 별칭에는 미묘한 차이점이 있다.

type Person = {
    name: string;
    age: number;
}

interface User {
    name: string;
    age: number;
}

let kimcoding: Person = {
    name: '김코딩',
    age: 30,
}

let coding: User = {
    name: '김코딩',
    age: 30,
}

 

 

 

위와 같이 코드들이 작성하는 동안 정의한 타입이나 인터페이스 내부에 어떤 프로퍼티가 들어가 있는지 기억이 나지 않을 경우가 있다.

 

  • 그래서 kimcoding이 참조하고 있는 Person 타입과, coding이 참조하고 있는 User 인터페이스에 각기 마우스를 올려놓아 보았다.

 

  • VSCode로 작성 시, kimcoding 객체가 참조하고 있는 Person에 마우스를 올리면 Person 내부에 어떤 프로퍼티들이 정의되어 있는지 보인다.
  • 그러나 coding 객체가 참조하고 있는 User에 마우스를 올리면 User 내부에 어떤 프로퍼티들이 정의되어 있는지 보이지 않는다.

따라서 타입 별칭으로 정의한 타입으로 작성할 시에는 조금 더 편하게 코드를 작성할 수 있다.

 

 

 

또 이러한 차이점이 있다.

  • 타입 별칭은 말 그대로 타입에 새로운 이름을 부여하는 것에서 그치기 때문에 확장이 되지 않는다.
  • 그러나 인터페이스는 확장이 가능하다.
  • 인터페이스는 기존의 인터페이스 및 타입 별칭으로 만들어진 타입 둘 다 상속할 수 있기 때문에, 유연한 코드 작성을 위해서는 인터페이스로 만들어서 필요할 때마다 확장할 수 있다.
type Person = {
    name: string;
    age: number;
}

interface User {
    name: string;
    age: number;
}

//에러가 발생합니다.
type Students extends Person {
    className: string;  
}

//정상적으로 동작합니다.
interface Students extends User {
	 className: string;   
}

//정상적으로 동작합니다.
interface Students extends Person {
    className: string;  
}

 

기존에 타입 별칭으로 정의한 타입을 상속하여 새로운 인터페이스를 정의할 수 있다.

type Person = {
    name: string;
    age: number;
}

interface Students extends Person {
    className: string;  
}

 

  인터페이스 타입별칭
사용목적 객체의 구조를 정의하기 위함 타입 이름(닉네임)을 참조하기 위함 
활용성 객체를 구성하는 프로퍼티나 메서드를
명시하고 유지하는데 유용
복잡한 타입을 단순화하고,
공통적으로 사용되는 타입을 중복없이 정의하기 위함 
확장 가능성 가능 불가능

 

타입 추론(Type Inference)

 

타입 추론은 TypeScript가 코드를 해석해 나가는 동작을 의미한다. 

  • TypeScript는 정적타입을 지원하는 프로그래밍 언어이다. 정적타입 시스템을 사용하면 코드의 안정성을 높이고 디버깅을 용이하게 할 수 있다.
  • TypeScript는 타입 추론(Type Inference)이라는 기능을 통해 코드 작성을 도와준다.

 

타입 추론(Type Inference)은 변수나 함수의 타입을 선언하지 않아도 TypeScript가 자동으로 유추하는 기능이다.

  • TypeScript은 타입을 따로 지정해주지 않아도 변수를 선언하거나 초기화할 때 타입이 추론된다.
  • 아래 코드에서는 변수 isNumber를 선언하고, 숫자 123을 할당했다.
  • 이 경우, 타입스크립트는 isNumber의 타입을 자동으로 숫자(Number)로 추론한다.
let isNumber = 123;

 

최적 공통 타입 (Best common type)

TypeScript는 여러 표현식에서 타입 추론이 발생할 때, 해당 표현식의 타입을 사용하여 "최적 공통 타입"을 계산한다. 

  • 아래에서 x 타입을 추론하려면 각 배열 요소의 타입을 고려해야 한다.
  • 여기서 배열의 타입으로 고를 수 있는 두 가지 후보가 있다: number와 null 
  • 최적 공통 타입 알고리즘은 각 후보의 타입을 고려하여, 모든 후보의 타입을 포함할 수 있는 타입을 선택한다.
    • number와 null은 서로 호환되지 않는 타입이다.
    • 최적 공통 타입 알고리즘은 두 가지 타입 중 가장 구체적인 타입인 number | null을 선택한다. 
    • 이는 number와 null을 모두 포함하는 유니온 타입이며 따라서, 배열 x의 타입은 number | null이 된다.
let x = [0, 1, null];

 

문맥상의 타이핑(Contextual Typing)

타입스크립트에서 타입을 추론하는 또 하나의 방식은 바로 문맥상으로 타입을 결정하는 것이다.

  • 문맥상의 타이핑(타입 결정)코드의 위치(문맥)를 기준으로 일어난다.  
  • 아래의 add 함수는 두 개의 매개변수를 받아 더한 값을 반환한다.
  • 하지만 매개변수의 타입이 명시되어 있지 않다.
    • 이 경우, 타입스크립트는 매개변수 a와 b의 타입을 자동으로 추론한다.
    • 만약 매개변수 a와 b가 모두 숫자(Number) 타입이라면, add 함수의 반환 값도 숫자(Number) 타입으로 추론된다.
function add(a, b) {
  return a + b;
}

 

타입 추론의 장점

이런 타입 추론에는 몇 가지 장점이 있다.

  1. 코드의 가독성 향상:
    • 타입 추론을 사용하면 코드의 가독성이 향상된다.
    • 명시적으로 타입을 지정하지 않아도 코드에서 변수의 타입을 알 수 있기 때문이다.
  2. 개발 생산성 향상:
    • 타입 추론을 사용하면 코드 작성 시간을 단축할 수 있다.
    • 명시적으로 타입을 지정하지 않아도 TypeScript가 자동으로 타입을 추론하기 때문이다.
  3. 오류 발견 용이성:
    • 타입 추론을 사용하면 코드의 오류를 발견하는 것이 쉬워진다.
    • TypeScript는 변수나 함수의 타입을 추론하여 타입 검사를 수행하기 때문이다.

 

타입 추론의 단점

그러나 타입 추론이 늘 좋은 것만은 아니다. 몇 가지 단점도 있다.

  1. 타입 추론이 잘못될 경우 코드 오류 발생:
    • 타입 추론은 TypeScript가 자동으로 수행하는 것이기 때문에, 추론이 잘못될 경우 코드 오류가 발생할 수 있다.
  2. 명시적인 타입 지정이 필요한 경우가 있음:
    • 타입 추론만으로는 부족한 경우가 있다.
    • 특히, 복잡한 함수나 객체의 경우에는 명시적인 타입 지정이 필요할 수 있다.

따라서, 타입 추론은 TypeScript의 장점 중 하나이지만, 때에 따라 명시적인 타입 지정이 필요한 경우도 있다.

 

타입 단언(Type Assertion)

 

타입 단언은 TypeScript에서 특정 값의 타입을 개발자가 명시적으로 지정하는 것을 말한다. 

  • 타입 단언을 사용하면 TypeScript 컴파일러는 해당 값을 지정된 타입으로 취급합니다.
    • 타입 단언은 개발자가 타입을 잘못 추론하거나 컴파일러가 타입을 올바로 추론하지 못하는 상황에서 유용하다. 
    • 또한 외부 라이브러리와 상호작용하거나 타입 변환을 수행할 때도 사용될 수 있다.
  • 하지만 타입 단언은 컴파일러에게 추가적인 정보를 제공하는 것이지, 실제로 값의 타입을 변환하는 것은 아니다.
  • 따라서 올바르지 않은 타입 단언은 런타임 시 에러를 발생시킬 수 있으므로 주의해야 한다.

 

타입 단언은 아래와 같이 두 가지 형태로 사용할 수 있다.

 

"angle-bracket" 문법 (<타입>)

  • someValue는 any 타입으로 선언되어 있다.
  • any 타입은 모든 타입과 호환되므로, 문자열로 간주될 수 있는 값도 할당할 수 있다.
  • 하지만 우리는 someValue가 실제로 문자열이라고 확신하고 있다.
let someValue: any = "hello";
let strLength: number = (<string>someValue).length;

 

"as" 문법

  • 따라서 타입 단언을 사용하여 (someValue as string)과 같이 명시적으로 타입을 지정했다.
  • 이를 통해 컴파일러에게 someValue를 문자열로 다룰 것이라고 알려준다.
let someValue: any = "hello";
let strLength: number = (someValue as string).length;

 

타입 단언이 쓰이는 경우

 

1. 타입 변환(Type Casting):

타입 단언은 타입 변환에 사용될 수 있다.

  • 예를 들어, 외부 라이브러리를 사용하는 경우 해당 라이브러리는 any 타입을 반환할 수도 있다.
  • 이때 타입 단언을 사용하여 반환된 값을 원하는 타입으로 변환할 수 있다.
  • 아래의 예시에서 someExternalLibraryFunction은 any 타입을 반환하는 외부 라이브러리 함수이다. 
  • 우리는 이 값을 MyCustomType으로 변환하기를 원한다. 
  • 이를 위해 타입 단언을 사용하여 result를 MyCustomType으로 강제로 지정한다.
// result 변수를 MyCustomType으로 변환한 뒤, 그 결과를 transformedResult 변수에 할당

let result: any = someExternalLibraryFunction();
let transformedResult: MyCustomType = result as MyCustomType;

 

2. 리액트 컴포넌트에서 프롭스 타입 지정:

리액트 컴포넌트에서 타입 단언을 사용하여 프롭스(props)의 타입을 지정할 수 있다.

  • 컴포넌트에 필요한 프롭스가 있을 때, 타입 단언을 사용하여 해당 프롭스의 타입을 명시할 수 있다.
  • 위의 예시에서 MyComponent는 props를 인자로 받는 리액트 함수 컴포넌트이다. 
  • props의 타입을 MyComponentProps로 명시하기 위해 타입 단언을 사용한다. 
  • 이를 통해 name과 age 프로퍼티에 접근할 때 타입 안전성을 확보할 수 있다.
interface MyComponentProps {
  name: string;
  age: number;
}

function MyComponent(props: any): JSX.Element {
  const { name, age } = props as MyComponentProps;
  .
  .
  .
    return <div>{name}, {age}</div>;
}


3. DOM 엘리먼트 조작:

DOM 엘리먼트를 조작할 때도 타입 단언을 사용할 수 있다.

  • 예를 들어, 특정 엘리먼트를 선택하고 해당 엘리먼트의 타입을 명시적으로 지정하는 데 유용하다.
  • 위의 예시에서 getElementById 함수는 HTMLElement 타입을 반환한다.
  • 하지만 우리는 myElement가 HTMLInputElement로 실제로 동작한다고 확신하고 있다.
  • 따라서 타입 단언을 사용하여 myElement를 HTMLInputElement 타입으로 지정한다.
const myElement = document.getElementById("myElement") as HTMLInputElement;

 


 

결론적으로 컴파일러에게 값의 타입을 명시적으로 지정하고 싶은 경우, 타입 단언 대신 타입 어노테이션(Type Annotation)을 사용하는 것이 바람직하다. 

  • 타입 단언은 개발자가 컴파일러에게 타입을 강제로 지정할 때 사용되며, 주의하여 사용해야 한다.
  • 타입 어노테이션은 변수나 함수 매개변수, 반환 타입 등에 직접 타입 정보를 명시하는 것이다. 
  • 이를 통해 TypeScript 컴파일러는 정적 타입 검사를 수행하고 타입 오류를 발견할 수 있다.
728x90
⬆︎