8. void 타입 불일치
interface A {
talk: () => void;
}
const a: A = {
talk() {return 3;},
};
const b = a.talk() // b는 void 타입
const b = a.talk() as unknown as number; // number 반환
❓ as unknown as number: TypeScript의 타입 안전성을 우회하여 반환값을 강제로 number로 처리 하지만, 절대적으로 필요한 경우가 아니라면 사용을 피하는 것이 좋다.
9. declare
// 다른 스크립트에서 정의된 전역 변수를 TypeScript에 알림
declare const VERSION: string;
declare let API_KEY: string;
// 타입이 정의되지 않은 외부 모듈 선언
declare module 'my-untyped-module' {
export function doSomething(): void;
export const value: number;
}
// 커스텀 전역 DOM 요소 선언
declare namespace JSX {
interface IntrinsicElements {
'custom-element': any;
}
}
// 윈도우 객체에 커스텀 프로퍼티 추가
declare global {
interface Window {
myCustomProperty: string;
myCustomMethod(): void;
}
}
declare는 주로 다음과 같은 상황에서 사용
- JavaScript로 작성된 외부 라이브러리의 타입 정의
- 전역으로 사용 가능한 변수나 함수의 타입 선언
- 컴파일된 JavaScript에는 포함되지 않을 타입 정보 정의
- 프로젝트에서 사용하는 타입 정의 파일(.d.ts) 작성
이러한 선언들은 실제 구현을 포함하지 않으며, TypeScript 컴파일러에게 타입 정보만을 제공.
10. any, unknown
const b: any = a.talk();
b.method(); // 어떤 메서드든 호출 가능, 타입 체크 안 함
b.whatever(); // 이것도 됨
b + 1; // 이것도 됨
- any는 모든 타입 검사를 비활성화
- 어떤 속성이나 메서드든 호출 가능
- 타입 안전성을 완전히 포기하는 것이므로 권장되지 않는다
- 레거시 코드나 타입을 알 수 없는 외부 라이브러리와의 호환성을 위해 사용
const b: unknown = a.talk();
b.method(); // 오류! unknown 타입은 직접 사용할 수 없음
(b as A).talk(); // 타입 단언 후에만 사용 가능
// unknown 타입을 안전하게 사용하는 방법
if (typeof b === 'string') {
console.log(c.toUpperCase()); // OK
}
if (b instanceof Date) {
console.log(c.toISOString()); // OK
}
- unknown은 타입스크립트의 type-safe 버전의 any
- 값의 타입을 모를 때 사용하는 안전한 방법
- 직접 메서드를 호출하거나 속성에 접근할 수 없다
- 타입 체크나 타입 단언 후에만 사용할 수 있다
11. 타입가드
타입가드: 조건문 안에서 타입 범위를 한정시켜줄 수 있는 방법
11-1. 타입별 타입가드
function numOrStr(a: number | string) {
if (typeof a === "string") {
return a.split(",");
}
if (typeof a === "number") {
a.toFixed(1);
}
}
numOrStr("123");
numOrStr(1);
11-2. 배열 타입가드
function numOrNumArray(a: number | number[]) {
// number[]
if (Array.isArray(a)) {
a.concat(4);
// number
} else {
a.toFixed(3);
}
}
numOrNumArray(123);
numOrNumArray([1, 2, 3]);
11-3. 클래스 타입가드
class A {
aaa() {}
}
class B {
bbb() {}
}
function aOrB(param: A | B) {
if (param instanceof A) {
param.aaa();
} else {
param.bbb();
}
}
11-4. 'in'연산자를 활용하여 속성명으로 타입가드
type bB = {type: 'b', bbb : string};
type cC = {type: 'c', ccc : string};
type dD = {type: 'd', ddd : string};
function typeCheck (param: bB|cC|dD){
if('bbb' in param){
param.type;
}else if('ccc' in param){
param.type
}else{
param.type
}
}
12. readonly
interface A {
readonly a: string;
b: string;
}
const aaaa: A = { a: "hello", b: "world" };
aaaa.a = "123"; // 변경 불가
13. 인덱스드 시그니처
: 인덱스 시그니쳐(Index Signature)는 {[key : T] : U}형식으로 객체가 여러 Key를 가질 수 있으며 Key와 매칭되는 value를 가지는 경우 사용
type B = { [key: string]: string };
const b: B = { "1": "123" };
14. 제네릭
: 함수를 선언할 때 말고 함수를 사용할 때 타입을 지정해줄 수 있게 하는 역할
// 1. 문자열 또는 숫자를 받는 함수. add('1',2) 같은 상황 때문에 에러 발생
function add(x: string | number, y: string | number): string | number {
return x + y;
}
// 1의 문제를 해결하기 위해 함수를 나눴으나, 두 번 선언했기 때문에 에러가 발생
function add(x: string, y: string): string {
return x + y;
}
function add(x: number, y: number): number {
return x + y;
}
⚠️ 위 상황을 해결하기 위해 나온게 제네릭
제네릭 타입 제한 방법
1. <T extends {...}>
function extendsSample<T extends { a: string }>(x: T) {
return T;
}
extendsSample({ a: "1" });
2. <T extends any[]>
function extendsSample<T extends any[]>(x: T) {
return T;
}
extendsSample(["1", "2", 3]);
3. <T extends (...args: any) => any>
function extendsSample<T extends (...args: any) => any>(x: T) {
return T;
}
extendsSample((x)=>console.log(x));
4. <T extends abstract new (...args: any) => any> // 생성자
function extendsSample<T extends abstract new (...args: any) => any>(x: T) {
return T;
}
class A {}
extendsSample(A);