러닝 타입스크립트
? 와 undefined 유니언은 엄밀히 틀리다.
interface Fruit {
cost: number | undefined;
}
// Property 'cost' is missing in type '{}' but required in type 'Fruit'.
const x: Fruit = {};
// Good!
const x: Fruit = { cost: undefined };
타입스크립트는 구조적 타이핑이기 때문에 더 많은 속성을 가졌더라도 구조가 맞으면 재할당 된다. 하지만 선언시점에서는 초과속성을 검사한다.
문제 없음
type Animal = {
name: string;
color: string;
};
let other = {
name: "이름",
color: "색깔",
arr: [],
};
let animal: Animal = other;
에러
type Animal = {
name: string;
color: string;
};
let animal: Animal = {
name: "이름",
color: "색깔",
arr: [], // 오류
};
타입 별칭을 잘 정의해 놓고 활용하면 타입이 복잡해지는 경우 타입에러 메세지를 인지하는데 큰 도움이 된다.
타입스크립트가 에러로그를 보유줄때 장황히 긴 타입을 보여주면, 개발자는 정확히 인지하기 힘들다.
never 타입
never타입은 어떠한 값도 할당할 수 없는 상태다
원시타입을 교차타입으로 설정하면 never타입이 되며 어떠한 값도 할당할 수 없는 상태가 된다.
리턴타입의 void와 never는 틀리다.
never 타입은 오류발생 처리나 무한루프를 의미할때 사용하면 유용하다.
함수 오버로드
함수 오버로드는 유용하지만 코드가 복잡해지기 때문에 최후의 순단으로 사용하는게 좋다.
배열타입 유추
타입스크립트는 배열의 타입을 유추할때 가변배열로 유추하려고 한다.
const a = ['문자열']; // type is "string[]"
변수값을 애너테이션으로 지정하는 경우에는 let으로 변수를 지정했다고 하더라도 초기값을 꼭 할당하지 않아도 된다.
타입스크립트는 배열에 타입을 지정하지 않고 빈배열로 설정하면 any[] 타입이 되며 이후에 push 되는 값에 따라 타입을 다시 유추한다.
튜플타입 with as const 어설션
튜플타입은 명시적으로 애너테이션해야 한다. 왜냐하면 타입스크립트는 가변길의 배열로 판단하여 유추하려는 본능이 있기 때문이다.
as const 어설션은 읽기전용의 불변튜플을 만들어 주며 다른 배열에 직접 할당도 불가능하게 해준다.
튜플을 반환할땐 as const 로 어설션 하여 반환하면 편리하다.
콜 시그니처
함수 표현은 객체 시그니처로도 사용할 수 있다 왜냐하면 함수 자체가 객체이기 때문이다.
일반 인덱스 시그니처에 속해있는 함수 표현과 틀린점은 함수명이 없다는 것.
type Operation2 = { (a: number, b: number): number };
const add2: Operation2 = (a, b) => a + b;
인덱스 시그니처
인덱스 시그니처란 동적으로 키가 올수 있는 객체를 말한다.
동적인 키를 수용해야 할때는 Map 을 사용하는게 더 안정적이므로 Map 사용을 고려하는 것도 좋다. Map은 값의 undefined를 고려하여 이미 타입이 설계되어 있다.
인터페이스 병합
인터페이스 병합은 속성에 대해서는 중복을 허용하지 않지만 메서드에 대해서는 오버로드를 허용한다.
오버로딩되어 병합됨 (메서드 이기 때문에)
interface Merged {
different(input: string): number;
}
interface Merged {
different(input: number): number; //Ok
}
에러남 (속성이기 때문에a)
interface Merged {
different: (input: string) => number;
}
interface Merged {
different: (input: number) => number;
}
클래스
타입스크립트에서의 클래스는 클래스 자체가 값이자 타입이된다.
클래스의 타입은 구조적타이핑을 따르므로 속성의 종류만 일치한다면 같은 타입으로 유추한다.
클래스에서 하위클래스의 생성자를 생략하면 부모 클래스의 생성자를 사용한다.
클래스는 상속을 통한 오버로딩은 불가능
타입스크립트는 상속을 통한 오버로딩은 불가능하다. (인터페이스에서는 가능하다)
클래스는 인자타입과 반환타입이 일치하지 않으면 오버라이딩 불가능
오버라이딩도 부모타입의 인자와 반환타입이 일치해야 오버로드 가능하다.
상속을 이용한 속성 오버라이딩도 부모타입과 속성이 일치해야 오버라이딩 된다.
(인터페이스에서는 가능하다)
어설션
타입 어설션은 권장되지는 않지만 불가피하게 필요한 경우도 많다.
맨뒤에 느낌표를 붙이년 어설션 방식을 non-null 어설션이라고 한다. 특정타입이 null을 갖지 않음을 명시하는 것이다.
어설션과 애너테이션은 용도가 틀리므로 구분해서 사용하는게 좋다.
원시타입의 타입어설션은 허용안되므로 강제로 변경하려면 as unknown as 타입으로 변경할수 있지만 권장하지는 않는다.
유용한 as const 어설션
as const 어설션은 원시타입의 경우에는 리터럴 타입으로 바꿔주며, 배열의 경우는 튜를로, 객체의 경우는 읽기전용으로 바꿔준다.
제너릭
제너릭은 get<T, K extends keyof T> 처럼 구체적으로 extends를 활용하여 타입을 좁혀주는게 좋다. 그렇지 않으면 리턴타입이 모든 키에 대해 리턴될수있는 유니언 타입으로 타입유추된다.
아래는 제한자 extends 없이 사용한 예
const getValue = <T>(obj: T, k: keyof T) => obj[k];
const person = {
name: "Aatrox",
age: 26,
};
// Error: Argument of type '"name1"' is not assignable to parameter of type '"name" | "age"'.
getValue(person, "name1");
// name1: string | number
const name1 = getValue(person, "name");
// age1: string | number
const age1 = getValue(person, "age");
Promise 의 then을 통과하면 프로미스의 타입이 바뀔수 있으므로 주의해야한다.
제네릭을 무분별하게 사용하면 코드가 복잡해지기만 할뿐 이점이 없으므로 주의깊게 사용해야 한다.
제너릭의 타입 매개변수는 적어도 하나의 매개변수와 리턴타입에서 같이 사용되어야 의미가 있다.
타입 선언 파일
d.ts 파일은 런타임에서 사용되는 코드를 제외한 순수한 타입만 정의되는 선언 파일이다.
선언 파일은 런타임값을 직접 만들수는 없지만 declare를 통해 타입 시스템에 외부에서 사용하는 내부구조를 타입시스템에 알릴수 있다.
인터페이스와 같은 타입은 d.ts내에서 declare없이도 허용되지만 다른 런타임으로 전역에서 사용될 값들은 꼭 declare를 해줘야 한다.
파일에서import 나 export 가 없는 파일에서의 값들은 전부 전역으로 취급된다.
모듈 파일에서도 global 키워드를 사용하면 전역이 된다.
.lib 타입 선언 파일
기본으로 제공되는 *.lib 타입 파일은 tsconfig의 target의 값에 따라 결정된다.
모듈 타입 정의
declare module "모듈명"을 사용하면 모듈의 타입을 정의할 수 있다.
*.module.css 처럼 모듈 이름에 와일드카드 처리도 가능하다.
타입스크립트는 타입스크립트로 작성된 모듈의 외부 노출을 위한 선언파일을 자동으로 생성해주는 옵션을 제공한다.
<T=unknown> 과 같은 트릭을 사용하면 jsx와 제너릭이 충돌하는 상황을 피할수 있다.
tsconfig 설정
resolveJsonModule
resolveJsonModule 은 타입스크립테에서 json파일을 읽어올 수 있게 한다.
target
target 옵션은 변환될 코드의 문법지원 버전으로 변경
emitDeclarationOnly
tsc가 emitDeclarationOnly 오직 선언파일만 만든다.
declarationSourceMap
declarationSourceMap 선언파일에 대한 소스맵을 만든다.
lib
lib은 지원되는 개발환경에 api지원에 관한 타입검사 모듈이다. 간단히 말하면 target은 트랜스파일 에 관한 옵션이고 lib은 폴리필 지원에 관련된 옵션이다.
skipLibCheck
skipLibCheck 는 외부라이브러리의 타입은 체크하지 않고 무시할수 있는 옵션이다.
stricNullCheck
stricNullCheck 가 비활성화 되도록 하면 모든 타입이 nullish 로 동작한다.
useUnknownInCatchVariables
useUnknownInCatchVariables 캐치하는 에러코드가 에러가 아닌 unknown 으로 처리되도록 한다.
module
module 옵션은 모듈을 가져오고나 내보내는 방식의 모듈참조 시스템을 결정
moduleResolution
moduleResolution 은 모듈을 찾아내는 방식을 의미하며 node와 nodeNext가 주로 사용된다.
nodeNext는 package.json의 entry속성을 사용할수 있지만 package.json에 명시된 type속성에 따라 확장자를 전부 표시해줘야 하는 단점이 있다.
esModuleInterop
esModuleInterop은 기본 내보내기를 지원하지 않는 외부모듈을 사용할수 있게 해준다.
allowSyntheticdefaultimports
allowSyntheticdefaultimports는 esModuleInterop 을 사용하는 경우 타입시스템이 이를 알리는 역할을 한다.
isolatedmodules
isolatedmodules 는 각각의 파일의 의존성을 고려하지 않고 독립적으로 컴파일 함으로서 다른 트랜스파일러와의 호완성을 높인다.
allowjs
allowjs 는 .js 파일도 import 해올수 있도록 해준다. 또한 jsdoc과도 호완된다.
열거형
타입스크립트를 개발하는 개발자들도 열거형보다는 const 객체를 선호하며 열거형 객체에 keyof typeof 를 이용해 값에 대한 리터럴 유니온을 가져오면 값을 체크하는 용도로 사용하기 편리하다.
네임스페이스
네임스페이스는 es모듈이 나오면서 현재는 거의 사용되지 않지만 jquery와 같은 모듈들은 타입을 정의할때 여전히 네임스페이스가 필요하다.
매핑된 타입
매핑된 타입은 기존 객체타입을 바탕으로 새로운 객체타입을 만들수 있는 방법이다. in 키워드를 사용한다.
일반적인 사용예는 리터럴 유니온 값들을 키로 갖는 새로운 객체타입을 생성하는 것이다.
객체를 비슷한 버전으로 복사하는데 편리하다.
매핑된 타입은 메서드를 속성으로 취급하니 주의해야한다.
매핑된 타입을 사용할때 ? 표 앞에 + 나 -를 붙이면 제한자를 추가하거나 뺀다는 의미다.
자주 사용되는 유용한 매핑타입의 활용은 미리정의되어 유틸리티 타입으로 제공된다. Partial, Required 등등
조건부 타입
조건부타입이란 삼항연산자처럼 조건부로 타입을 추론하여 사용할 수 있다.
조건부타입분산
- 조건부 타입은 유니온에 분산된다(제네릭 타입에 유니온이 오면 타입을 각각 추론하여 생성한다).
조건부 타입은 재귀로 만들수도 있다.
각각의 모든 속성을 전부 검사하여 조건부로 바꿀 수 있다. 아래처럼
type MkeFunc<T> = { [key in keyof T]: T[key] extends (...args: any[]) => any ? T[key]: () => T[key]; };
템플릿 리터럴 타입
type Aa = 'hello${string}' 처럼 사용하여 특정 문자열로 지작되는 패턴을 타입화 할 수 있다.
동적으로 오는 타입의 자리에는 모든 리터럴타입을 전부 사용 가능하다.