typescript는 javascript의 상위 집합 → 문법의 오류와는 별개 ( typescript에서는 오류여도 컴파일은 가능 )
→ 모든 js파일은 ts / 모든 ts 파일이 js 파일은 아님
ts 컴파일러는 js 파일에도 유용함 ( 모든 오류를 잡아주진 못하지만 type checker 덕분에 의도하지 않은 오류를 찾아낼 수 있음 )
하지만 의도를 모두 알아챌 수 없으므로 interface를 생성하는 것을 권장함
interface State {
name: string
capital: string
}
const states: State[] = [
...
]
js에서는 오류가 나지 않을 구문도 ts에서는 타입 추론을 하지 못해 오류가 나기도 함
→ 이상하게 쓸 거면 쓰지 말아라 … 안전하게 써라 !
const a = null + 7; // JS에서는 7이 되지만 오류 발생
const b = [] + 12; // JS에서는 '12'가 되지만 오류 발생
type system은 runtime 안전성을 보장해주지만 정적 타입의 정확성을 보장하지는 않음
typescript는 tsc 실행 시 CLI에서 100여개의 옵션 설정 가능
혹은 tsconfig.json에서 설정 가능
noImplicitAny
: any로 추정되는 타입에 any나 다른 타입을 명시하지 않으면 오류 발생strictNullChecks
: null check를 엄격하게 할 것인가 ( null과 undefined를 모든 타입에서 사용할 수 있게 않도록 할 것인지 )const el = document.getElementById('status');
if ( el ) {
el.textContent = 'Ready'; // null check를 통해 확인
}
Non-null Assertion
Operator ( Null이 아닌 어선셜 연산자 )```tsx
el!.textContent = 'Ready';
```
Definite Assignment Assertsions
( 확정 할당 어선셜 )```tsx
let x!: number;
console.log(x + x); // 값 할당 전이지만 사용할 수 있도록 함
```
참고 자료 : https://developer-talk.tistory.com/191
tsc → js로 컴파일
, 타입 체크
의 두 가지 동작을 하지만 이 둘은 완벽히 독립적 !
따라서 타입 오류가 있는 코드도 컴파일 가능 ( type의 오류는 다른 언어의 warning과 유사함 )
Interface 기반 변수의 타입 체크를 할 때는 Interface 자체로 사용 불가 ( js로 컴파일 시 interface가 없으므로 )
interface 내부 타입 변수
사용하기
// Interface 기반 변수의 타입 체크를 할 때는 Interface 자체로 사용 불가
// ( js로 컴파일 시 interface가 없으므로 )
interface Square {
kind: 'square' // 타입을 명시
width: number
}
interface Rectangle {
kind: 'rectangle'
height: number
width: number
}
type Shape = Square | Rectangle
function calculateArea(shape: Shape) {
if ( shape.kind === 'rectangle' ) {
return shape.width * shape.height;
} else {
return shape.width * shape.width; // rectangle을 소거하고 sqaure로 추론됨
}
}
calculateArea({
height: 100,
width: 100,
// kind: 'square' -> 오류 발생
kind: 'rectangle'
})
Interface 대신 Class
사용하기
class Square2 {
constructor( public width: number ) {}
}
class Rectangle2 extends Square2 {
constructor( public width: number, public height: number ) {
super(width);
}
}
// class는 클래스 값이면서도, type으로써도 존재할 수 있음
type Shape2 = Square2 | Rectangle2
function calculateArea2(shape: Shape) { // shape은 class의 instance
if ( shape instanceof Rectangle2 ) {
return shape.width * shape.height;
} else {
return shape.width * shape.width; // rectangle을 소거하고 sqaure로 추론됨
}
}
function asNumber(val: number | string): number {
return val as number; // as는 런타임에 영향을 주지 못하는 타입 연산자
}
위의 코드는 아래와 같은 js로 컴파일됨 → 즉 as 연산은 runtime에 영향을 주지 못하며, 정제 불가능
function asNumber_js(val: any) {
return val;
}
// 따라서 아래와 같이 작성해야지 원하는 방식으로 작동하게 된다
function asNumberReal(val: number | string): number {
return typeof val === 'string' ? Number(val) : val;
}
type으로써의 선언 시에만 오버로딩 가능 !!
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any) {
return a+b;
}
runtime overhead는 없지만 build overhead
가 존재함 ( tsc의 속도가 느림 )
오버 헤드가 과하게 길어질 경우 타입 체크 없이 빌드하는 방법이 존재하기는 함 …
TypeScript의 타입 호환성은 구조적 서브타이핑( structural subtyping )
을 기반으로 함
구조적 타이핑 = 오직 멤버반으로 타입을 관계시키는 방식
즉 y가 x와 동일한 멤버를 가지고 있다면 x와 y는 호환된다는 것
TypeScript 타입 시스템 뜯어보기: 타입 호환성
구조적 타이핑에 익숙해진다면 오류 분류에 도움이 됨
interface Vector2D {
x: number
y: number
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
// name 속성이 추가된 별도의 interface
// extends를 활용하여 관계성을 선언해도 동일함
interface NamedVector {
name: string
x: number
y: number
}
const v1: NamedVector = { x: 3, y: 4, name: 'Zee' };
// x: number, y: number를 충족하므로 알아서 계산
calculateLength(v1); // result = 5
이런 식으로 구조가 호환된다면 type 오류가 발생하지 않음
이는 테스트 과정 등에서는 편리하지만, 문제가 생길 수도 있음
ex) 없는 데이터에 대해 연산을 스킵하여 원치 않는 결과가 나옴
Any 타입에는 타입 안전성이 없다 !!
// any 타입은 타입 안전성을 보장하지 않음
let ageAny: number
// ageAny = '12' -> 할당 불가능
ageAny = '12' as any // ok
ageAny += 1 // ok ... 혼란의 시작, ageAny = "121"