타입스크립트는 자바스크립트의 슈퍼셋이다.
기존에 존재하는 자바스크립트 코드를 타입스클비트로 마이그레이션 하는 것은 기존 코드에서 일부분에만 타입스크립트 적용이 가능하기 때문에 엄청난 이점이라고 할 수 있다.
모든 자바스크립트 프로그램은 타입스크립트이지만, 모든 타입스크립트 프로그램이 자바스크립트는 아니다. 타입스크립트에는 타입을 명시하는 추가적인 문법들이 존재한다.
타입 시스템의 목표 중 하나는 런타임에 오류를 미리 찾아내는 것인데, 타입스크립트가 ‘정적’ 타입 시스템이라는 것이 이런 특징 때문이다. 타입스크립트는 타입 구문 없이도 오류를 잡아낼 수 있지만, 적절한 타입을 추가하면 더 많은 오류를 찾아낼 수 있다.
tsconfig.json
은 타입스크립트 설정 내용이 포함되어있는 설정 파일이다. 이 중에 noImplicitAny
와 strictNullChecks
옵션을 제대로 이해해야 설정을 제대로 사용할 수 있다.
noImplicitAny
는 변수들이 미리 정의된 타입을 가져야하는지 여부이다. 타입스크립트는 타입 정보를 가질 때 가장 효과적이기 때문에 되도록이면, noImplicitAny를 설정하는 것이 좋다. 게다가 문제를 발견하기 수월해지고, 코드의 가독성이 좋아지며, 개발자의 생산성이 향상되는 효과를 기대할 수 있다.
strictNullChecks
는 null
과 undefined
가 모든 타입에서 허용되는지 확인하는 설정이다. 이 옵션은 null
이나 undefined
과 관련된 오류를 잡아 내는 데 많은 도움이 되지만, 코드 작성이 어려워진다는 단점이 있다. 그래서 자바스크립트 마이그레이션과 같은 과정이라면 사용하지 않는 편이 나을 수 있다.
tsconfig.json
을 사용하는 것이 낫다.noImplicitAny
를 설정하는 것이 좋다.undefined
는 객체가 아닙니다와 같은 오류를 방지하려면 strictNullChecks
를 설정하는 것이 좋다.타입스크립트의 컴파일러는 두 가지 역할을 한다.
중요한 것은 위의 두 가지 역할이 완벽히 독립적이다. 위 두가지 역할에서 볼 때 타입스크립트의 가능한 부분과 불가능한 부분을 추론할 수 있다
컴파일은 타입 체크와 독립적으로 동작하기 때문에 타입 오류가 있는 코드도 컴파일 가능하다. 타입스크립트에서는 문제가 될 부분을 알려주지만, 빌드를 멈추지는 않는다. 타입스크립트 컴파일러는 유효한 자바스크립트이면 컴파일을 해낸다. 오류가 뜨면 컴파일 오류가 아니고 타입 체크 오류라고 할 수 있다.
런타임에 타입 체크가 불가능하지만 타입 정보를 유지하는 방법으로 태그된 유니온(tagged union)
이 있다. 해당 기법을 사용하면 런타임에 타입 정보를 쉽게 유지할 수 있다. 예시는 아래와 같다.
interface Square {
kind: 'square'
width: number
}
interface Rectangle {
kind: 'rectangle'
height: number
width: number
}
type Shape = Square | Rectangle
function asNumber(val: number | string): number {
return val as number
}
as number
와 같은 타입 연산은 런타임 동작에는 아무런 영향을 미치지 않는다. 그렇기 때문에 값을 정제하기 위해서는 런타임의 타입을 체크해야 하고 자바스크립트 연산을 통해 변환을 수행해야 한다.
런타임 타입과 선언된 타입이 맞지 않을 수 있다. 선언된 타입이 언제든지 달라질 수 있다는 것을 명심해야 한다.
타입스크립트에서는 타입과 런타임의 동장이 무관하기 때문에, 함수 오버로딩은 불가능하다.
function add(a: number, b: number) {
return a + b
}
// ~~~ Duplicate function implementation
function add(a: string, b: string) {
return a + b
}
// ~~~ Duplicate function implementation
하나의 함수에 대해 여러 개의 선언문을 작성할 수 있지만, 구현체(implementation)은 하나 뿐이다.
타입과 타입 연산자는 자바스크립트 변환 시점에 제거되기 때문에, 런타임 성능에 아무 영향을 주지 않는다. 런타임 오버헤드가 없는 대신 빌드타임 오버헤드가 있다.
자바스크립트는 덕타이핑 기반이다. 타입 체커의 타입에 대한 이해도가 사람과 조금 다르기 때문에 가끔 예상치 못한 결과가 나오기도 한다.
덕타이핑은 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식이다.
타입스크립트의 타입 시스템은 코드에 타입을 조금씩 추가할 수 있기 때문에 점진적이고, 언제든지 타입 체커를 해제할 수 있기 때문에 선택적이다. 이 때, any
가 핵심 역할을 하는 타입이다. any
를 사용하면 타입 선언을 안해도되고, 에러를 간단히 해결해 줄 수 있기 때문에 많이 쓰고싶을 수 있지만, 특별한 경우를 제외하고는 any
를 사용하면 타입스크립트의 장점을 누릴 수 없게 된다.
가장 작은 집합은 아무 값도 포함하지 않는 공집합이며, 타입스크립트에서는 never
타입이다. 그다음 작은 집합은 한 가지 값만 포함하는 리터럴 타입이다. 두 개 혹은 세 개로 묶을 땐, 유니온(union)타입을 사용한다.
type A = 'a' // 리터럴 타입
type AB = 'a' | 'b' // 유니온 타입
interface Identified {
id: string
}
interface Person {
name: string
}
interface Lifespan {
birth: Date
death?: Date
}
type PersonSpan = Person & Lifespan
const ps: PersonSpan = {
name: 'Alan Turing',
birth: new Date('1912/06/23'),
death: new Date('1954/06/07'),
} // OK
& 연산자는 두 타입의 인터섹션이고, Person과 LIfespan을 둘 다 가지는 값은 인터섹션 타입에 속하게 된다. 다시 말해, 각 타입 내의 속성을 모두 포함하는 것이 일반적인 규칙이다.
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
조금 더 효과적으로 선언하는 방법은 아래와 같이 extends
키워드를 하는 것이다. 이것은 ‘~의 부분집합’ 이라고 이해하면 된다.
interface Person {
name: string
}
interface PersonSpan extends Person {
birth: Date
death?: Date
}
타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중의 한 곳에 존재한다.
interface Cylinder {
radius: number
height: number
}
const Cylinder = (radius: number, height: number) => ({ radius, height })
function calculateVolume(shape: unknown) {
if (shape instanceof Cylinder) {
shape.radius
// ~~~~~~ Property 'radius' does not exist on type '{}'
}
}
instanceof
는 자바스크립트의 런타임 연산자이고, 값에 대하여 연산하기 때문에 타입이 아닌 함수를 참조하여서 오류가 발생한 것이다. 한 심벌이 타입인지 값인지는 언뜻봐서는 알 수 없고, 문맥을 살펴봐야 한다.
class
와 enum
은 상황에 따라 타입과 값 모두 가능하다. 클래스가 타입으로 쓰일 때는 형태(속성과 메서드)가 쓰이고, 값으로 쓰일 때는 생성자가 사용된다. typeof
같은 경우는 타입 공간에서는 보다 큰 타입의 일부분으로 사용 가능하고, 값의 관점에서는 런타임의 typeof
연산자가 된다.
type
과 interface
는 타입 공간에만 존재한다.class
나 enum
은 타입과 값 두 가지로 사용될 수 있다.typeof
,this
그리고 많은 다른 연산자들과 키워드들은 타입 공간과 값 공간에서 다른 목적으로 사용될 수 있다.출처