[Typescsript] 타입스크립트 올인원:Part-1(3)

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);