[TypeScript 독학] #11 타입스크립트 교과서(1)

안광의·2023년 10월 22일
0

TypeScript 독학

목록 보기
11/12
post-thumbnail

시작하며

개발자로서 일을 시작하면서 타입스크립트를 실무에서 사용한 지 1년 반 정도가 되었다. 기본적인 문법과 타입에 대해서 공부하고 그때 그때 부족한 부분을 채우면서 사용을 했었는데, 『타입스크립트 교과서』 라는 실전에 초점을 맞힌 책이 출간되어서 정리해 보려고 한다.

이전에 몰랐던 내용이나 알고 있었지만 중요하다고 생각되는 부분들만 정리해서 작성하였다.



{} 타입

type {}nullundefined를 제외한 모든 타입을 의미한다.
(빈 interface도 동일하게 동작)


let의 타입 추론

let a = null; //let a: any
let b = undefined; //let b: any

let으로 선언한 변수에 null이나 undefined를 할당하면 any 타입으로 추론한다.


튜플 타입

  • 튜플 타입은 push, pop, shift, unshift 메서드 사용이 가능하다.

  • 튜플 타입에 spread 문법 사용 가능하다.

  • sperad syntax, 구조 분해 할당을 해도 타입 추론이 가능하다.

  • 튜플 타입에도 ?(옵셔널) 수식어를 사용할 수 있다.

let tuple: [number, boolean?, string?] = [1, false, 'hi'];

class 타입

class는 타입으로도 사용이 가능하다.

class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const person: Person = new Person('Jonn');

any 타입

  • 빈 배열(any[])에 요소를 추가하면 타입이 바뀌지만 제거한다고 해서 이전 타입으로 돌아가지 않는다.
const arr = []; //const arr: any[]
arr.push('1');
arr; //const arr: string[]
arr.pop();
arr; //const arr: string[]
  • keyof anynumber | string | symbol이다.

fetch와 JSON.parse

fetch, JSON.parse 는 명시적으로 any를 반환한다.

fetch('url')
  .then((response) => response.json())
  .then((result) => { //(parameter) reult: any
  });

const result = JSON.parse('{"hello":"json"}'); //const result: any

void 타입

() => void 타입은 반환값이 달라도 무시한다.

  • 사용자가 함수의 반환값을 사용하지 못하도록 제한한다.
  • 반환값을 사용하지 않는 콜백 함수를 타이핑할 때 사용한다.

interface 선언 병합

interface는 재선언을 통한 선언 병합이 가능하다

interface Merge {
  one: string
}
interface Merge {
  two: number;
}
const example: Merge = {
  one: '1',
  twp: 2.
};

namespace

  • namespace 내부에 타입을 선언할 수 있고 export를 사용하여 외부에서 사용할 수 있다.
namespace Example {
  export interface Inner {
    test: string;
  }
  export type InnerTwo = number;
}
const ex1: Example.Inner = {
  test: 'hello'
}
const ex2: Example.InnerTwo = 123;
  • namespace도 이름이 같다면 interface와 마찬가지로 병합된다.

  • namespace를 중첩해서 사용할 수 있고, 내부 namespaceexport해야 한다.

  • namespace 내부에 실제 값을 선언했다면 namespace 자체를 자바스크립트 값으로 사용할 수도 있다.

  • namespacedeclare로 선언하면 이미 다른 곳에 실제 값이 있다고 생각해서 내부 구현부를 생략할 수 있다.


-로 수식어 제거하기

수식어 앞에 -를 붙이면(ex) -readonly) 해당 수식어를 제거된 채로 속성을 가져올 수 있다.

interface Original {
  readonly name?: string
}

type Copy = {
  -readonly [key in keyof Original]-?: Original[key];
}

컨디셔널 타입 분배법칙

  • 검사하려는 타입이 제네릭이면서 유니언이면 분배법칙이 일어난다.
type Start = string | number;
type Result<Key> = Key extends string ? Key[] : never;
let n: Result<Start> = ['hi']; //let n: string[]

//Result<string | number> => Result<string> | Result<number> => string[] | never => string[]
  • 배열로 타입을 감싸면([T])로 분배 법칙이 일어나지 않는다.

매개변수 나머지 문법

  • 나머지 매개변수 문법은 배열의 전개 문법과는 달리 매개변수 마지막 자리에만 위치해야 한다.
function example1(a: string, ...b: number[]) {}
example('hi', 123, 4, 56);

this 타입

  • this의 타입은 매개변수의 첫 번째 자리에 표기하면 되고 다른 매개 변수들은 한 자리씩 뒤로 밀려난다.
function example(this: Window) {
  console.log(this);
}
  • 메서드를 갖고 있는 객체는 this가 자기 자신으로 추론되기 때문에 this가 바뀌는 경우를 제외하고 명시적으로 타이핑하지 않아도 된다.

  • 클래스나 인터페이스의 메서드는 this를 타입으로 사용할 수 있다.


오버로딩

여러 오버로딩에 동시에 해당될 수 있는 경우는 제일 먼저 선언된 오버로딩이 적용되므로 오버로딩의 순서는 좁은 타입에서 넓은 타입 순으로 해야 한다.


콜백 함수

  • 콜백 함수의 매개변수는 생략 가능하다.

  • 콜백 함수의 반환값이 void일 때 어떠한 반환 값이 와도 상관 없다(ex) Array.forEach)


공변성과 반공변성

특성설명
공변성A->B 일때 T<A>->T<B>
반공변성A->B 일때 T<B>->T<A>
이변성A->B 일때 T<A>->T<B> 이면서 T<B>->T<A>
무공변성A->B 일때 T<A>->T<B>, T<B>->T<A> 모두 안되는 경우
  • 함수<반환값>으로 표현 시 a->b일때 T<a> ->T<b>이므로 함수의 반환값은 공변성을 갖는다.(strict 옵션 관계없이 항상)

  • 매개변수<반환값>으로 표현 시 b->a일때 T<a> ->T<b>이므로 함수의 매개변수는 반공병성을 갖는다.(strict 옵션 해제 시 이변성)

  • 함수(매개변수): 반환값으로 선언시 매개변수가 이변성을 가지고 함수:(매개변수) => 반환값으로 선언 시 매개변수가 반공변성을 가진다.


클래스

  • 타입스크립트는 생성자 함수 방식으로 개체를 만드는 것을 지원하지 않는다.(new를 붙여서 호출할 수 있는 것은 class가 유일)

  • class는 타입이자 값이 되지만 인스턴스의 타입이 되므로 class 자체의 타입이 필요하다면 typeof를 사용해야 한다.


new 인터페이스

인터페이스에서 new 연산자를 붙이면 클래스 생성자를 타이핑 할 수 있다.

interface Person {
  new (name: string): {
    name: string;
  };
}

enum 타입

  • enum에 다른 숫자를 할당하면 다음 값은 저절로 이전 값에서 1을 더한 값으로 할당된다.
enum Level {
  NOVICE = 3,
  INTERMEDIATE, //4
  ADVANCED = 7
  MASTER, //8
}
  • enum에 숫자가 아닌 문자열을 할당하면 그 다음 값부터는 모두 직접 값을 할당해야 한다.

infer

  • 타입 변수(infer E에서 E)는 컨디셔널 타입에서 참 부분에서만 사용할 수 있다.
type El<T> = T extends (infer E)[] ? never : E //X, Cannot fine name 'E'.
  • 같은 이름의 타입 변수(반환값도 동일)는 서로 유니언이 되고 매개변수인 경우에는 인터섹션이 된다.
type Union<T> = T extends { a: infer U, b: infer U } ? U : never;
type Result1 = Union<{ a: 1 | 2, b: 2 | 3 }> //type Result1 = 1 | 2 | 3

type Intersection<T> = T extends {
  a: (pa: inter U) => void,
  b: (pb: inter U) => void,
} ? U : never;
type Result2 = Intersection<{
  a(pa: 1 | 2): void,
  b(pb: 2 | 3): void,
}>; //type Result2 = 2
  • 반환값과 매개변수가 같은 타입 변수를 사용한 경우 반환값 타입이 매개변수 타입의 부분 집합인 경우에만 교집합이 되고 그 외에는 never가 된다.

타입 좁히기

타입 좁히기는 자바스크립트에서도 실행할 수 있는 코드여야 하기 때문에 자바스크립트 문법을 사용해야 한다.


재귀 타입

재귀 타입은 타입 인수로 사용할 수 없다.

type T = number | string | Record<string, T> //X
type T = number | string | {[key: string]: T} //O

satisfies 연산자

타입스크립트 4.9 버전에 추가된 satisfies 연산자를 사용하여 타입을 추가적으로 검사할 수 있다.

const universe = {
  sun: 'start',
  sriius: 'start', //sirius 오타로 에러 발생
  earth: { type: 'planet', parent: 'sun' },
} satisfies {
  [key in 'sun' | 'sirius' | 'earth']: { type: string, parent: string } | string
};

타입 주장

타입을 주장할때는 그 타입이 일시적이므로, 변수에 적용해야만 타입이 유지된다.


& 연산자

객체 타입이 아니더라도 & 연산자를 사용할 수 있다.(ex) 브랜딩 기법)

type Brand<T, B> = T & { _brand: B };
type KM = Brand<number, 'km'>;
type Mile = Brand<number, 'mile'>;


마치며

지금까지 2장 기본 문법 익히기를 정리하였는데, 알지 못했던 내용들이 많았다. 실무에서는 아직 복잡한 타입을 선언할 기회가 없어서 접할 기회가 없던 연산자나 타입스크립트가 동작하는 방식에 대해서 알 수 있었고, 추후에 패키지를 만들때 타입 정의에 활용할 수 있는 기능들을 배울 수 있었다. 책의 뒷부분에는 lib.es5.d.ts의 내부 코드나 실제 라이브러리나 환경에서 타입을 사용하는 방법들이 설명되어 있어서 이후 내용도 정리해 볼 예정이다.

profile
개발자로 성장하기

0개의 댓글