Today I Learned_230309
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 정의의 제네릭 부분을 유심히 보자.