Dev/JS Family, HTML, CSS

[TypeScript] 타입스크립트 입문(5) - 타입 시스템

youngst 2022. 9. 17. 03:09

타입 시스템

동적 타입과 정적 타입

타입 체커가 컴파일 타임에 수행되는지, 런타임에 수행되는지에 따라 동적 타입과 정적 타입으로 나뉜다.

 

정적 타입 - 컴파일 타임에 수행

  • 개발자가 타입을 명시적으로 타이핑해야하는 언어
      -> Java, C#, C++ 등
  • 타입 체커와 타입 추론을 통해 일부 타이핑을 생략할 수 있는 언어
      -> TypeScript, Scala, Haskell 등
  • 컴파일 타임에 타입 검사를 수행하기 때문에 보다 빠르게 오류를 잡아낼 수 있다
      -> 런타임 에러 방지 가능!
  • !!자동 완성!!을 통해 데이터 타입 혹은 속성에 대한 확신을 가질 수 있다!!!

 

동적 타입 - 런타임에 수행

  • 동적 타입 시스템은 런타임 상황에도 타입이 동등한지 확인한다.
      -> JavaScript, Python, Ruby, Perl, PHP 등
  • JavaScript는 느슨한 타입의 동적 타입 언어
    • 변수는 어떠한 타입과도 연결되지 않는다.
    • 프로그램이 실행 중에 어떤 타입의 값으로든 할당하거나 재할당 할 수 있으며, 타입을 변경하거나 객체에 새로운 속성이나 메서드를 추가할 수 있다. -> 간편하지만 에러를 잡아내기 어렵고 안전한 코딩이 힘들다!
interface Person {
  name: string
  age: number
}
//인터페이스 덕분에 자동완성이 가능!
const obj: Person = {
  name: 'N',
  age: 99
}

function getPersonInfo({name, age}: Person) {
  obj.age
  obj.name
}

 

 

구조적 타입과 명목적 타입

타입스크립트가 타입을 이해하는 방법

구조적 타입 (Structural Type)

  • 객체가 어떤 속성들을 가지는지
  • 즉, 구조를 기준으로 타입을 따진다. - 구조가 같다면 타입이 같다!(주의)
  • 기본적으로 자바스크립트(& 타입스크립트)는 구조적 타입의 언어이다.
interface Person {
  name: string
  age: number
}

interface Animal {
  name: string
  age: number
}

const me: Animal = {
  name: 'Jang',
  age: 99
}

function getName(obj: Person) {
  return obj.name
}

getName(me) //error 발생 안 함 - 구조는 똑같기 때문!
getName({ name: 'hello', age: 10 }) //error 발생 안 함

 

명목적 타입 (Nominal Type)

  • 각 타입이 고유하다는 것을 의미
  • 즉, 동일한 타입이나 데이터가 있더라도 타입을 공유할 수 없다.
  • 이름 기반으로 타입을 따진다.
  • 작성한 타입이 런타임에 존재한다.
  • C#, Java 등

 

덕 타이핑

  • 타입스크립트의 타입은 구조적 타입
  • 타입의 생김새가 오리와 같다면 그것은 오리의 타입임을 뜻한다.
      -> 이름이 다르더라도 오리처럼 보이고, 오리처럼 수영하고, 오리처럼 꽥꽥거리면 그것은 오리일 것이다.
  • 거위가 오리와 같은 속성을 가지고 있다면 그것조차도 오리 타입!
interface Duck {
  name: string
  swim: Function
  home: 'river'
}
// 오리와 거위는 같은 타입!
interface Goose {
  name: string
  swim: Function
  home: 'river'
}

 

상위 타입과 하위 타입

  • TypeScript의 모든 타입은 집합일 뿐이다.
  • 때문에 타입 간에도 계층이 존재한다.
  • 타입 간의 관계는 특정 관계로 선언되었는지 여부가 아닌 포함된 속성에 의해 결정된다.
  • 최상위 타입은 시스템에서 허용하는 모든 가능한 값(타입)을 설명하는 타입
  • 서브 타입은 슈퍼(상위) 타입에 할당할 수 있다.
      -> 반대는 불가능!

상위 타입(Super Type)

  • any
  • unknown

하위 타입(Sub Type)

  • 상위 타입과 하위 타입 사이에는 무수히 많은 타입이 존재한다.
  • never

타입 캐스트

  • 타입 변수를 다른 타입의 변수에 할당하며 생기는 타입 오류로, 그 계층을 확인할 수 있다.
  • extend 키워드 이용

업 캐스트

  • 상위 타입에 하위 타입을 할당
  • 안전한 상황으로 인지하여 컴파일러 오류없이 대부분 암시적으로 가능

다운 캐스트

  • 하위 타입에 상위 타입을 할당
  • 안전하지 않은 상황으로 인지하여 컴파일러 오류 발생
  • any는 예외
/**
 * 상위 타입과 하위 타입
 */
let anyType: any
anyType = 'string'
anyType = 123
anyType = true

let unknownType: unknown
unknownType = 'string'
unknownType = 123
unknownType = true

//상위 타입 하위 타입 구분 방법
type A = Array<string> extends Object ? true : false //true
type B = Function extends Object ? true : false //true

interface Animal {
  name: string
}

interface Person {
  name: string
  age: number
}

//Upcast, Downcate 가능 여부 판별
type PersonAndAnimal =  Animal extends Person ? true : false //false

// Array와 function은 Object의 하위 타입
let obj: Object
obj = []
obj = function() { }

 

 

건전성 (Soundness)

타입스크립트의 건전한 타입 시스템 개념

  • 건전함의 반대인 불건전함(Unsoundness)은 런타임에 문제를 유발할 수 있지만, 타입스크립트는 일부 불건전한 동작을 허용한다.
  • 컴파일 타임에 알기 어려운 특정 타입을 안전하게 허용하는 것을 의미한다.
  • 즉, 컴파일러가 런타임 시점 값의 타입을 보장할 수 있다는 개념
  • 이는 모든 JavaScript 코드를 지원하기 위함!
  • 건전한 언어는 데이터가 타입이 말하는 것과 일치하는지 확인하기 위해 때때로 런타임 검사를 사용
  • TypeScript는 변환된 코드가 런타임에 영향을 미치지 않는 것을 목표로 한다.
/**
 * 건전성 (Soundness)
 */
const x = [0, 1, 2]
//불건전하다..
x[10].toPrecision()

// any
let strOrNum:any = 999;
strOrNum = 'Hello'
//any는 불건전을 허용
strOrNum.toPrecision(9)

/**
 * 타입 단언
 * 
 * 사용자가 타입을 가장 잘 알고 있다는 것을 명시하는 방법
 */
const cardNumber = '1000' as unknown as number

/**
 * Non-null assertion operator
 */
interface Home {
    foods?: string[]
    books?: string[]
}

const MyHome:Home = {}
// !. -> 확실하게 있다는 것을 개발자가 보장(런타임 검사 생략)
MyHome.foods!.push('banana')
MyHome.books!.push('Clean Code')