TS. Part1 Ch02-lib.es5.d.ts 분석

hun2__2·2023년 8월 2일
0

02.1 - forEach, map 제네릭 분석

TS에서 타입추론을 하는 방법은 제네릭 덕분이다.

TS에서 제네릭 타입을 추론할 때 사용된 제네릭 타입 중 가장 첫번째로 알고 있는 값을 기준으로 보고 모든 제네릭 타입을 그 타입으로 추론한다.

(내생각) 제네릭은 약간 함수의 매개변수 같은 느낌이다.

제네릭으로 명시된 타입을 사용하는 밖에서 실제 값이 들어오면(인자) 제너릭 타입 안에서 제네릭 타입으로 명시된 값들에 실제 값의 타입이 들어간다(매개변수)

interface Array<T> {
  forEach(
    callbackfn: (value: T, index: number, array: T[]) => void,
    thisArg?: any
  ): void;
}

// forEach
[1, 2, 3].forEach((value) => console.log(value));
["1", "2", "3"].forEach((value) => console.log(value));

하지만 TS가 타입추론을 잘 못한다면 타입을 알려줄 수도 있다.(타입 파라미티)

interface Array<T> {
  map<U>(
    callbackfn: (value: T, index: number, array: T[]) => U,
    thisArg?: any
  ): U[];
}

// map
const strings = [1, 2, 3].map((value) => value.toString());
const numbers = [1,2,3].map<number>((v) => v*1)

먼저 [1,2,3]은 number[]이므로 T는 number임을 알 수 있고,

callbackfn의 return 값이 string 이므로 U도 string인 것을 알 수 있다.

💡 주의! 타입파라미터는 값뒤에 < > 이고, 값 앞에 < > 는 갖에 형변환이다.(as)

02.2 - filter 제네릭 분석

같은 함수가 여러가지 방법으로 사용되는 경우에는 타입이 여러가지로 오버로딩 되어있다.

제네릭은 타입찾기다. 하나씩 비교하며 타입이 확정되면 그것을 기준으로 동일한 제네릭이 모두 같은 타입이 된다.

interface Array<T>{
    filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
    filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
}
// T가 number가 되므로 S도 number가 되고
const filterfn1 = [1,2,3,4,5].filter(v => v%2)

// (string| number)[]으로 제대로 타입분석을 하지 못하고 있다. (T가 string| number이기 때문)
const filterfn2 = ["1","2","3",4,5].filter(v => typeof v === 'string')
// 아래 타입의 경우 제네릭이 T밖에없기 때문에 string| number를 바꿀 수 없다.
// 하지만 위에 타입경우 T는 고정되어있지만 S를 바꿀 수 있는 여지가 있기 때문에 위에 타입으로 추론해야한다.

// string extends string|number 이기 때문에 커스텀 타입가드를 이용해서 S를 string으로 고정해주면 원하는 타입을 얻을 수 있다.
const predicate = (value:string|number) : value is string =>typeof  value === 'string'
const filterfn3 = ["1","2","3",4,5].filter(predicate)

하지만 여기서는 map에서 했던것 처럼 S 타입을 바로 지정해 줄 수 없다.

왜냐하면

filter<S extends T>(*predicate*: (*value*: T, *index*: number, *array*: T[]) => *value* is S, *thisArg*?: any): S[]; 타입의 predicate 는 return 값이 value is S로 커스텀 타입가드이기때문에

["1","2","3",4,5].filter<string>(*v* => typeof *v* === 'string')이렇게 해주면 return 값이 S로 타입가드(형식조건자)가 아니라서 error가 난다.

02.3 - forEach 타입 직접 만들기

내가 쓸 타입을 interface로 만들어 주고 그 안에 메서드로 forEach를 만들어 본다

  1. 내가 사용할 타입 1개를 호환가능 하게 만들기
  2. 여러 타입 가능하게 제네릭으로 만들기
  3. 코드가 업데이트 됨에 따라 타입도 업데이트 해주기
interface Arr<T>{
    forEach(callbackfn : (item : T) => void): void
//     정답
//     forEach(callbackfn: (value: number, index: number, array: Int8Array) => void, thisArg?: any): void;
}

const myForEach1 : Arr<number> = [1,2,3]

myForEach1.forEach((item) => console.log(item))

const myForEach2 : Arr<string> = ["1","2","3"]
myForEach2.forEach((item) => console.log(item))

02.4 - map 타입 직접 만들기

내가 원하는 코드를 보고 타입을 짤 수 있어야 한다.

array의 원소들의 타입인 T는 알 수 있지만 map의 반환타입은 T와 다를 수 있기 때문에 map을 사용하는 순간 타입이 지정되는 새로운 제네릭 S가 필요하다

// T는 map을 사용하기 전에 타입지정, S는 사용하는 순간의 타입 지정
interface Arr<T> {
  map<S>(callBackFn: (v: T) => S): S[];
}

const myMap: Arr<number> = [1, 2, 3];
const myMapResult1 = myMap.map((v) => v * 1);
const myMapResult2 = myMap.map((v) => v + "");
const myMapResult3 = myMap.map((v) => !!v);

const myMap2: Arr<string> = ["1", "2", "3"];
const myMapResult4 = myMap.map((v) => +v);

02.5 - filter 타입 직접 만들기

number | string이 T인데 number | string이 아닌 새로운 타입이 필요하므로 새로운 제네릭 S를 추가해준다(filter를 쓸 때 결정되므로 filter에 붙여준다)

이때 v 는 T인데 v is S 가되려면 S가 T의 부분집합이여야 가능하기 때문에 S extends T로 제네릭 S를 제한해준다.

interface Arr<T> {
  filter<S extends T>(callbackFn: (v: T) => v is S): S[];
}

const a: Arr<number> = [1, 2, 3];
const myFilter = a.filter((v): v is number => v < 2); // [1]

const b: Arr<number | string> = [1, 2, "3"];
const myFilter2 = b.filter((v): v is string => typeof v === "string"); // ['3']

reduce타입 직접 만들기

interface Arr<T> {
  reduce(callBackFn: (a: T, b: T) => T, init?: T): T;
  reduce<S>(callBackFn: (a: S, b: T) => S, init?: S): S;

  //   정답
  // reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T;
  // reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue: T): T;
  // reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U, initialValue: U): U;
}

const a: Arr<number> = [1, 2, 4, 5, 3];

const myReduce1 = a.reduce((a, b) => (a += b)); // 15
const myReduce2 = a.reduce((a, b) => (a += b), 10); // 25

const b: Arr<number | string> = [1, 2, "4", "5", 3];
const myReduce3 = b.reduce<number>((a, b) => {
  return typeof b === "number" ? (a += b) : a;
}); // 6
const myReduce4 = b.reduce<number>((a, b) => {
  return typeof b === "number" ? (a += b) : a;
}, 10); // 16

const c = [1, 2, "4", "5", 3];

const solReduce1 = c.reduce<number>((a, b, idx, arr) => {
  return typeof b === "number" ? (a += b) : a;
}, 10); // 25

// 초기값 필수..?
const solReduce2 = c.reduce<number>((a, b, idx, arr) => {
  return typeof b === "number" ? (a += b) : a;
}); // 25

02.6 - 공변성과 반공변성

공변성 반공변성은 함수간에 서로 대입할 수 있는지 유무를 따지는 것임

결론

리턴값은 더 넓은 타입으로 대입이 가능하다 (공변성)

매개변수는 리턴 값이랑 반대로 좁은 타입으로 대입이 된다. (반공변성)

// a를 b에 대입 

function a(x: string | number): number {
  return 0;
}
type B = (x: number) => number | string;
let b: B = a;

cf) 타입스크립트에서의 타입들은 기본적으로 공변성 규칙을 따르지만, 유일하게 함수의 매개변수는 반공변성을 갖고 있다. (tsconfig의 strictFunctionTypes 옵션이 true 일때의 기준)

📘 타입스크립트 공변성 & 반공변성 완벽 이해

02.7 - 하나에는 걸리겠지(오버로딩)

한번에 내가원하는 타입을 모두 표현할 수 없으면 다 작성하자(오버로딩)^^

interface, class 안에서 모두 오버로딩 가능하다

cf) declare는 타입 정의만하고 함수 구현은 안해도 된다.(어디선간 해야함)

02.8 - 타입스크립트는 건망증이 심하다(+에러 처리방법)

타입스크립트는 타입변환을 강제로 해줘도 밑에 바로 사용하려고 해도 안된다.

이럴경우 변수로 만들어서 사용해면 아래서도 사용할 수 있다.

interface Axios {
  get(): void;
}
class CustomError extends Error {
  response?: {
    data: any;
  };
}

declare const axios: Axios;

(async () => {
  try {
  } catch (err: unknown) {
    console.error((err as CustomError).response?.data);
    // 타입을 지정해준건 일회성이라 그 다음에 또 까먹는다
    err.response?.data; // error msg : 'err' is of type 'unknown'.

    // 그래서 변수를 만들어서 타입을 저장해준다. (as를 최대한 적게 쓰기 위함)
    const customError = err as CustomError;
    console.log(customError.response?.data);

    // as 안쓰기(best)
    if (err instanceof CustomError) {
      const customError2 = err;
      console.log(customError2.response?.data);
    }
  }
})();

const a = <T = unknown>(v: T): T => {
  return v;
};
const c = a(3);

cf) inteface는 JS에서 사라지기 때문에 instanceof를 사용할 수 없다 그래서 class로 만들어줘야함

profile
과정을 적는 곳

0개의 댓글