Today I Learned_230309

6 분 소요

TypeScript

인프런 타입스크립트 강의 수강

  • readonly : 실수로 바꾸는 것을 막아줌 (자바스크립트에서는 바꿀 수 있음)
interface A {
  readonly a: string;
  b: string;
}

const aaaa : A = {a:'hello', b:'world'}
aaaa.a = '123'; //여기서 이걸 바꿔줌
  • 인덱스드 시그니처
//이렇게 쓰면 너무 길다..
type A1 = {a: string, b:string, c:string, d:string};

//어떤 키든 간에 전부 문자열이고 그 값도 전부 문자열이다.
type A = {[key: string]: string};   
  • 맵드 타입스
  • 키가 이 셋 중 하나였으면 좋겠다 → 키를 줄여 놓는 것
type A = {[key in B] : number}
type B = 'Human' | 'Mammal' | 'Animal' //파이프는 인터페이스에서 사용 불가. 타입을 사용해야 한다.
const aaaa : A = {Human: 123, Mammal : 5, Animal : 7}
  • 클래스의 constructor
class A {
    a: string;
    b: number;
// 원래는 아래와 같이 constructor로 초기화 됨
    constructor() {
        this.a = '123';
        this.b = 123;
    }
}

//이렇게 줄여짐
class A {
    a: string = '123';
    b: number = 123;
}

//constructor는 매개변수로도 받을 수 있음
class A {
    a: string;
    b: number;
    constructor(a: string, b: number) {
        this.a = a;
        this.b = b;
    }
}

//constructor에 기본값을 줄 수 있음
class A {
    a: string;
    b: number;
    constructor(a: string, b: number = 123) {
// b는 항상 존재하기에 물음표를 안 붙여도 된다.
// constructor(a: string, b?: number = 123) 이러면 오류남
        this.a = a;
        this.b = b;
    }
}

const a = new A('123'); //constructor에 기본값이 있을 경우 이렇게 하나만 넣어도 됨
const a = new A('123', 456) //기본값을 덮어쓰게 됨
  • 클래스 이름은 그 자체로 타입이 될 수 있다.
  • 클래스 이름은 인스턴스를 가리킨다.
type AA = A; //여기서 A는 new A('123')을 가리킴
typeof A; //여기서 A는 클래스 A를 가리킴

//오류 나는 케이스
const b : typeof A = new A('123');
//오류가 나지 않는 케이스
const c : typeof A = A;
  • class에 private, protected 추가됨
  • private이면 자기 클래스 내부에서만 사용 가능
class A {
    private a : string = '123';     //ts에서 제공하는 private -> 추천
    #b : number = 123;              //js에서 제공하는 private

    method() {
        console.log(this.a, this.#b)
    }
}
  • private을 사용해야 protected와 구분이 된다.
  • 단 private을 사용하면 실제 코드에서는 public으로 변환됨 → 하지만 애초에 타입스크립트 단계에서 차단을 하므로 큰 문제는 없다.
  • interface, private, protected는 typescript상에서 보호하기 위한 것으로, js로 변환하면 사라져버린다.

  • 객체지향인 점을 ts가 따라함
  • 클래스의 모양을 인터페이스가 통제할 수 있다.
interface A {
    readonly a: string;
    b: string;
}

class B implements A {
    a: string = '123';
    b: string = 'world';
}
class B implements A { //클래스는 인터페이스를 구현할 수 있다.
  private a: string;
  protected b: string;
}
class C extends B {}
new C().a;
new C().b;
  • 인터페이스는 추상, 클래스는 구현
  • 클래스는 그 자체로 타입이기 때문에 다른 쪽에서도 사용할 수 있음.
  • js에서 사라지면 안 될 경우 class, 사라져도 될 경우는 interface 사용
interface A {
    readonly a: string;
    b: string;
}

class B implements A {
    private readonly a: string = '123'; //인스턴스에서 접근할 수 없음. 클래스 내에서만 사용해야 한다.
    //private readonly 와 같이 복합적으로 섞어서 사용할 수 있음.
    protected b: string = 'world'; //안에서 가능, 인스턴스에서는 불가능, 상속받았을 때 사용 가능
    public c : string = 'wow'; // 기본. 안 써도 됨. 인스턴스에서 접근 가능한 특성. 안에서도 접근 가능

    method() {
        console.log(this.a);
    }
}

class C extends B {
    method() {
        console.log(this.a);    //접근 불가(private)
        console.log(this.b);    //접근 가능(protected)
        console.log(this.c);    //접근 가능(public)
    }
}
  • abstract class, abstract method
  • 추상 클래스도 메소드를 가질 수는 있다.
abstract class X {
  abstract work(user: User): boolean;
}
class Y extends X {
  work(user: User): boolean { //abstract로 된 것은 반드시 상속받아 구현해주어야 한다.
    return true;
  }
}
  • abstract class, abstract 생성자
const constructor: abstract new (...args: any) => any = ...
  • class vs interface

런타임에서 있냐 없냐.

  • optional
function abc(a: number, b?: number, c?: number?) {}
//function abc(...args: number[]) {}  -> 전부다 받고 싶을 때 사용
abc(1)
abc(1, 2)
abc(1, 2, 3)

let obj: { a: string, b?: string }  = { a: 'hello', b: 'world' } //b는 있어도 그만, 없어도 그만
obj = { a: 'hello' };
  • 제네릭은 타입에 대한 함수라고 생각하면 됨. 추론을 활용하기
  • 지금 현재 타입이 뭔지는 모르겠는데 나중에 정할래. 타입을 변수처럼 만드는 것.
  • T말고 다른 문자열을 적어도 되긴 하는데, T로 많이 정한다.
function add<T>(x: T, y: T): T { return x + y } //만들때 뭔 타입인지는 모르겠지만 실제 사용할 때 정해짐
//우리가 원한것은 add(1, 2), add('1', '2')인데, 이 대로 놔두면 add(true, false)도 가능해진다
add<number>(1, 2);
add(1, 2); //서로 같으면 가능
add<string>('1', '2');
add('1', '2');
add(1, '2');
  • 제네릭 선언 위치 기억하기
function a<T>() {}
class B<T>() {}
interface C<T> {}
type D<T> = {};
const e = <T>() => {};
  • 제네릭 기본값, extends
  • extends를 통해 제한할 수 있다.
function add<T extends string | number>(x: T, y: T): T { return x + y }
add(1, 2);
add('1', '2')

//제네릭을 여러개 동시에 만들면서 각각 다른 제약을 둘 수 있다.
function method<T extends number, K extends string>(x: T, y: K): T

// <T extends {...}> // 특정 객체
<T extends { a: string}>
// <T extends any[]> // 모든 배열
<T extends string[]>
// <T extends (...args: any) => any> // 모든 함수를 넣고 싶으면 any 써도 된다.
<T extends (a:string) => number> //콜백함수 지정
// <T extends abstract new (...args: any) => any> // 생성자 타입. 클래스 자체를 넣고 싶을 때
//abstract new (...args: any) => any -> 이건 생성자 타입에 해당
// <T extends keyof any> // string | number | symbol
  • 기본값 타이핑
  • 매개변수가 기본값일때, 기본값이 객체일 때 실수 조심하자.
  • 타입스크립트가 추론을 하지 못할 때는 기본값을 써주자.
const a = (b = 3, c = 5) => { //b와 c에 기본값을 주면 자동으로 타입을 추론함
    return '3';
}

//아래 두 개는 동일
const a = (b = {children: 'hello'}) => {
}
const a = (b :{children:string } = {children: 'hello'}) => {
}

//리액트에서는 이게 헷갈릴 수 있음
const add = <T>(x:T, y:T) => ({x, y});
//이러면 덜 헷갈려 한다.
const add = <T = unknown>(x:T, y:T) => ({x, y}); 
const add = <T extends unknown>(x:T, y:T) => ({x, y}); 
const add = <T ,>(x:T, y:T) => ({x, y}); 

lib.es5.d.ts 분석

제네릭을 쓸 수 있는 것들

  • 이름 뒤에 제네릭이 같이 온다.
  • interface Array, type A (type alias), class A
  • 제네릭은 자바스크립트로 변환될 때 사라진다.
  • 타입스크립트는 기본적으로 자바스크립트에 타입을 추가한 것 - 라이브러리를 분석하면서 보면 좋다.
  • 타입을 생각하기 전에 자바스크립트 코드를 알아야 한다.
  • 타입스크립트 분석 - 위치 분석이 중요하다.
interface Array<T> {
    forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
    map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; //map의 매개변수는 2개고 리턴값이 U다
    //T는 array의 타입, U는 콜백함수의 리턴값의 타입
}
type A<T> = string;
class B<T> {}

//---------------forEach 분석----------------------
const a : Array<number> = [1, 2, 3];
//value는 [1, 2, 3]의 형식(number)에 해당
[1, 2, 3].forEach((value) => {console.log(value)}); //콘솔에 1, 2, 3
['1', '2', '3'].forEach((value) => {console.log(value)}); //콘솔에 1, 2, 3
[true, false, true].forEach((value) => {console.log(value)}); //콘솔에 1, 2, 3
[1, '2', true].forEach((value) => {console.log(value)}); //콘솔에 1, 2, 3
//ts는 위의 타입들을 어떻게 파악하지? -> 제네릭으로 파악

//콜백함수 : callbackfn: (value: T, index: number, array: T[]) => void
//(item) => {console.log(item)} -> 이게 콜백함수다

function add<T>(x:T, y:T) : T {return x} //네 개 중 하나의 타입만 추론되어도 나머지 타입을 추론
add(1, 2) //둘 중 하나 타입이 있는지 확인. 하나가 number니까 나머지 자리도 number로 확인.

//제네릭의 타입을 직접 알릴 수 있다 -> 타입스크립트가 멍청해서 추론을 잘 해주지 못할 때 알려준다(타입 파라미터)
add<number>(1, 2);
<number>add(1, 2); //이건 제네릭이 아니라 타입 강제 변환에 해당.
//제네릭도 js로 옮겨가면 사라진다.

//----------------map 분석----------------------
[1, 2, 3].map((item) => item.toString()); //['1', '2', '3'] string[]
//타입스크립트가 item이 number인지 어떻게 잘 추론했을까? -> map 정의의 제네릭 부분을 유심히 보자.

카테고리:

업데이트: