본문 바로가기
개발공부_Blog/TypeScript

TypeScript는 구조적 타입 시스템을 따른다.

by 독서개발자 2023. 12. 22.

타입 시스템?

언어에서 사용할 수 있는 아주 여러가지 값들을 어떤 기준으로 묶어서 타입으로 정할지 결정하고 또 코드의 타입을 언제 검사할지 그리고 어떻게 검사할지 등의 우리가 프로그래밍 언어를 사용할때 타입과 관련해서 지켜야 하는 규칙들을 모아둔 체계다.

  • 동적 타입 시스템 ( 코드를 실행하고 나서 변수의 타입을 유동적으로 결정 )
  • 정적 타입 시스템 ( 코드 실행 전 변수의 타입을 고정적으로 결정 )
  • 점진적 타입 시스템

JavaScript 타입 시스템의 특징

(1) JavaScript는 동적 타입 시스템을 따른다.

기본적으로 변수나 함수의 타입들을 미리 정하지 않는다. 어떤 특정 타입과 연결되지 않으며, 런타임 중에 값이 수정될 수 있다. 이때 모든 타입의 값으로 할당과 재할당이 가능하며, 작업 중 타입에 오류가 발생하면 암시적으로 타입 변환도 가능하다. 이러한 동적 타입 시스템은 작업에 유연함을 가져다 주지만 개발자가 의도하지 않은 문제가 발생할 수 있다.

 

(2) JavaScript는 Duck typing 덕 타이핑 언어이다

덕 타이핑(Duck Typing)이란? 동적 타이핑의 한 종류로, 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 말한다. ( 객체가 어떤 타입에 부합하는 메서드와 변수를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식이다. )

만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다. [ 위키백과 ] 덕타이핑은 ‘덕 테스트’에서 유래했다

 

 

TypeScript 타입 시스템의 특징

(1) TypeScript는 점진적 타입 시스템을 따른다

정적 타입 시스템은 코드 실행 이전에 모든 변수의 타입을 고정적으로 결정한다. 그렇기 때문에 타입 관련 오류가 있으면 바로 에러를 발생해 실행 전에 문제를 발견할 수 있다는 장점이 있지만 모든 변수에 타입을 다 작성해야 하는 번거로움이 있고, 코드에 대한 유연성이 떨어진다.

타입스크립트는 코드 실행 전 변수의 타입을 검사하는 정적 타입 시스템을 따르면서 변수의 초기값에 의해 자동으로 타입을 추론해 주는 업그레이드 된 타입 시스템인 점진적 타입 시스템을 따른다.

(2) JavaScript의 상위 호환 언어이며, JS를 모델링 하기 위해 구조적 타입 시스템을 따른다

타입스크립트는 자바스크립트의 상위집합(superset)이다. 그렇기 때문에 자바스크립트의 모든 코드를 이해하며 동작한다. 자바스크립트의 덕타이핑의 특징을 그대로 따라서 모델링 해야 하기 때문에 구조적 타입 시스템을 기반으로 동작한다.

구조적 타입 시스템

구조적 타이핑은 객체나 인터페이스의 구조가 동일하면 해당 객체나 인터페이스가 같은 타입이라고 간주하는 타입 시스템이다. 즉, 객체의 내부 구조가 동일하면 서로 다른 소스에서 나왔더라도 같은 타입으로 간주된다. TypeScript는 구조적 타이핑을 기반으로 하고 있다.

  • 자바스크립트는 함수 표현식이나 객체 리터럴 같은 익명 객체를 광범위하게 사용하기 때문에 자바스크립트는의 타입은 명목적 타입 시스템보다는 구조적 타입 시스템을 이용하여 표현하는 것이 훨씬 더 자연스럽다.
// 구조적 타입 시스템
class Foo { method(input: string) {/* ... */ } }
class Bar { method(input: string) {/* ... */ } }

let foo: Foo = new Bar();// 작동합니다!

 

위의 예는 Foo와 Bar의 이름이 다르지만 구조는 완전히 동일하기 때문에 작동합니다. 이 모양/구조가 변경되면 오류가 발생다.

  • 명목적 타입 시스템 ? 명목적 타이핑은 변수나 객체의 타입이 이름(명칭)에 의해 결정되는 타입 시스템이다. 구조적으로 동일한 구조를 가지더라도 명시적인 명칭이 같아야만 두 객체가 같은 타입으로 간주된다.

 

TypeScript의 구조적 타이핑의 예

TypeScript의 구조적 타입 시스템의 기본 규칙은  y가 최소한 x와 동일한 멤버를 가지고 있다면 x와 y는 호환된다는 것입니다.

아래 코드를 예시로 들어보면 Vector2D의 타입이 NamedVector와 동일한 구조의 타입을 가지고 있다면 NambedVector는 Vector2D에 호환된다는 이야기다.

interface Vector2D {
  x: number;
  y: number;
}
interface NamedVector {
  name: string;
  x: number;
  y: number;
}

function calculateLength(v: Vector2D) {
  console.log(v);
  return Math.sqrt(v.x * v.x + v.y * v.y);
}

const v:NamedVector = { x: 3, y: 4, name: "thdud" };
console.log(calculateLength(v));

NamedVector와 Vector2D의 타입 관계를 정의하지도 않았는데 함수의 실행에서 Error가 발생하지 않았다. NamedVector의 type과 Vector2의 type이 호환되기 때문에 구조적 타이핑의 관점에서 보았을 때, 에러가 발생하지 않은 것이다.

쉽게 말하면 Vector2D 타입을 인수로 받는 함수에 NamedVector 타입을 가진 인수를 전달한다고 타입스크립트가 에러를 발생시키는 것은 아니라는 이야기다. Vector2D는 NamedVector를 포함할 수 있는(슈퍼타입) 타입이 되고 이는 구조적 관점에서 봤을 때 허용되는 상황이 된다.


reference

댓글