typescript

lib.es5.d.ts 분석하기 - 3.7 ~ 3.11

케케_ 2024. 11. 10. 21:23

목차

 

3.7 filter 만들기

filter 메소드 : 배열의 각 요소를 검사하여 주어진 조건을 만족하는 요소들로만 새로운 배열을 생성하는 메소드

 

array.filter(callback(element, index, array), thisArg?)

 

  • callback: 각 요소에 대해 실행할 함수로, 조건을 검사하는 로직을 포함
    • element: 배열의 현재 요소
    • index (선택적): 현재 요소의 인덱스
    • array (선택적): filter 메서드를 호출한 배열
  • thisArg (선택적): callback 함수 내부에서 this로 사용할 값
  • 불리언 반환: callback 함수는 조건을 만족하면 true, 그렇지 않으면 false 반환
  • 모든 요소를 검사: 배열의 모든 요소에 대해 callback 함수가 한 번씩 호출

 

1차 filter 메서드 타이핑

const r1 =[1,2,3].myFilter((v) => v < 2);           //numner[]
const r2 =[1,2,3].myFilter((v,i,a) => {});             //numner[]
const r3 =['1',2,'3'].myFilter((v) => typeof v === 'string');       //(string | number) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v) {     
    return v.num % 2;
});// {num:number}[]

interface Array<T> {
    myFilter(callback: (v: T, i: number, a: T[]) => void, thisArg? : any) : T[];//반환값 T[]
}

 

  • 메서드의 반환값 = T[] 
    • r2은 number[], r3 (string | number) []이 됨
    • 원래 각각 never[](이건 왜?), string[]이 되어야함!

 

2차 타이핑

- 새로운 타입 매개변수 추가

const r1 =[1,2,3].filter((v) => v < 2);           //numner[]
const r2 =[1,2,3].myFilter((v,i,a) => {});             //numner[]
const r3 =['1',2,'3'].myFilter((v) => typeof v === 'string');       //(string | number) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v) {     
    return v.num % 2;
});// {num:number}[]

interface Array<T> {
    myFilter<S extends T>(callback: (v: T, i: number, a: T[]) => void, thisArg? : any) : S[];
}
  • myFilter<S extends T>
    • 새로운 타입 매개변수는 배열 요소의 타입에 대입할 수 있어야하기 때문
    • map과 다르게 S는 T와 완전히 다를 순 없음
      • 기존 요소에 값을 추리는 것이기 때문
  • 여전히 달라지지 않음 => 직접 입력

 

3차 타이핑

- 콜백 함수가 타입 서술 함수의 역할

const r1 =[1,2,3].myFilter((v) => v < 2);          
const r2 =[1,2,3].myFilter((v,i,a) => {});            
const r3 =['1',2,'3'].myFilter((v) => typeof v === 'string');       //(string) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v) {     
    return v.num % 2;
});

interface Array<T> {
    myFilter<S extends T>(callback: (v: T, i: number, a: T[]) => v is S, thisArg? : any) : S[];
}
  • v is S
    • 반환값 부분에 is 연산자 사용
  • 하지만 모든 테스트에 에러 발생함 (r3은 안났음..) -> 모든 함수를 타입 서술 함수로 만들기

 

 

4차 타이핑

  • 타입 서술 함수 : 특정 타입을 판별하거나 타입을 좁히는 데 도움을 주는 함수
    • 변수 is 타입 형식
function isString(value: any): value is string {
    return typeof value === "string";
}

 - 함수가 true를 반환하면, TypeScript는 value string 타입이라고 추론

const r1 =[1,2,3].myFilter((v):v is number=> v < 2);          
const r2 =[1,2,3].myFilter((v,i,a): v is never => {});      //에러         
const r3 =['1',2,'3'].myFilter((v): v is string => typeof v === 'string');       //(string) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v): v is {num : number} {     
    return v.num % 2;       //에러
});

interface Array<T> {
    myFilter<S extends T>(callback: (v: T, i: number, a: T[]) => v is S, thisArg? : any) : S[];
}
  • 일부 에러 -> 타입 서술 함수는 boolean을 반환해야 하기 때문

 

5차 타이핑 -  콜백함수 수정

const r1 =[1,2,3].myFilter((v):v is number=> v < 2);          
const r2 =[1,2,3].myFilter((v,i,a): v is never => false);      //       
const r3 =['1',2,'3'].myFilter((v): v is string => typeof v === 'string');       //(string) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v): v is {num : number} {     
    return v.num % 2=== 1;       //
});

interface Array<T> {
    myFilter<S extends T>(callback: (v: T, i: number, a: T[]) => v is S, thisArg? : any) : S[];
}
  • 에러가 해결됨

 

(추가) r1, r4는 타입 서술 함수가 굳이 필요하지 않았음

 - 타입 서술 함수를 안써도 되도록 오버로딩 활용

const r1 =[1,2,3].myFilter((v) => v < 2);          
const r2 =[1,2,3].myFilter((v,i,a): v is never => false);      //       
const r3 =['1',2,'3'].myFilter((v): v is string => typeof v === 'string');       //(string) []
const r4 =[{num: 1}, {num: 2}, {num: 3}].myFilter(function(v){     
    return v.num % 2=== 1;       
});

interface Array<T> {
    myFilter<S extends T>(callback: (v: T, i: number, a: T[]) => v is S, thisArg? : any) : S[];
    myFilter(callback:(v: T, i: number, a: T[]) => boolean, thisArg? : any) : T[];	// T[] 반환
}

 

 

filter 메소드

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[];
  • 두번째 메소드 반환값이 unknown 
    • 실제로 필터 메서드가 반드시 boolean을 반환할 필요가 없기 때문

 

3.8 reduce 만들기

: 배열의 각 요소를 누적하여 하나의 결과값을 반환

: 배열을 하나의 값으로 줄이기 위해 반복적으로 콜백 함수를 실행

 

array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue);

 

  • callback: 배열의 각 요소에 대해 실행할 함수
    • accumulator: 이전 콜백의 반환값을 누적하는 변수로, 최종적으로 반환될 값 저장
    • currentValue: 현재 처리 중인 배열 요소
    • currentIndex (선택적): 현재 요소의 인덱스
    • array (선택적): reduce 메서드를 호출한 배열
  • initialValue (선택적): accumulator의 초기값으로, 제공되지 않으면 배열의 첫 번째 요소가 accumulator로 사용

 

메서드 타이핑해보기

 

: reduce 메서드는 콜백함수의 매개변수가 4개

: 누적값 a, 현재값 c, 인덱스 i, 원본 배열 arr

: 둘째 인수인 초기값이 있을 때와 없을 때 다르게 동작.  ---> 테스트 케이스 잘 만들 필요

 - 없을 때 => 첫번째 요소가 초기값

 

1차 타이핑

const r1 =[1,2,3].myReduce((a , c) => a +c);        //6        
const r2 =[1,2,3].myReduce((a , c , i, arr) => a +c , 10);      //  16     
const r3 =[{num: 1}, {num: 2}, {num: 3}].myReduce(
    function(a , c){     
        return { ...a, [c.num] : 'hi'};       
    },
    {},         //error
);      // {1 : 'hi, 2 : 'hi, 3 : 'hi}
const r4 = [{num: 1}, {num: 2}, {num: 3}].myReduce(
    function (a,c) {
        return a + c.num;   //error
    },
    '',     //error
)       //'123'


interface Array<T> {
    myReduce(callback: (a: T, c: T, i :number, arr : T[]) => T, iV? : T) : T;
   
}
  • 반환값의 타입이 요소의 타입과 같게 설정돼 있어 에러 발생
  • reduce 메서드의 반환값 -> 요소의 타입과 다를 수 있음. ==> 오버로딩 추가

 

2차 타이핑

const r1 =[1,2,3].myReduce((a , c) => a +c);        //6        
const r2 =[1,2,3].myReduce((a , c , i, arr) => a +c , 10);      //  16     
const r3 =[{num: 1}, {num: 2}, {num: 3}].myReduce(
    function(a , c){     
        return { ...a, [c.num] : 'hi'};       
    },
    {},         
);      // {1 : 'hi, 2 : 'hi, 3 : 'hi}
const r4 = [{num: 1}, {num: 2}, {num: 3}].myReduce(
    function (a,c) {
        return a + c.num;  
    },
    '',    
)       //'123'


interface Array<T> {
    myReduce(callback: (a: T, c: T, i :number, arr : T[]) => T, iV? : T) : T;
    myReduce<S>(callback: (a: S, c: T, i :number, arr : T[]) => S, iV : S) : S;
   
}
  • 초기값 있는 경우
    • 초기값의 타입이 최종 반환값 -> 타입 매개변수 S 선언
  • a, 콜백함수 반환값, 초기값 iV, 반환값 모두 S 

 

실제 reduce

reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;   
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
  • 3개의 오버로딩

 

3.9 flat 분석하기

: 중첩된 배열을 평탄화(flatten) 하여 하나의 배열로 만들어주는 메서드

: 배열의 모든 중첩된 요소를 지정된 깊이만큼 재귀적으로 이어붙여 새로운 배열을 반환

: 빈 요소 제거 - 배열의 빈 요소는 자동으로 제거

 

array.flat(depth);

- depth (선택적): 평탄화할 깊이를 지정하는 매개변수

  • 기본값은 1이며, 중첩 배열을 한 단계 평탄화
  • 깊이를 늘리면 더 깊은 중첩 배열을 평탄화할 수 있음
  • Infinity를 사용하면 모든 중첩 배열을 완전히 평탄화 가능

 

//깊이 1
const arr = [1, 2, [3, 4], [5, [6, 7]]];
const flatArr = arr.flat();

console.log(flatArr);  // 출력: [1, 2, 3, 4, 5, [6, 7]]
//단계 2
const arr = [1, 2, [3, 4], [5, [6, [7]]]];
const flatArr = arr.flat(2);

console.log(flatArr);  // 출력: [1, 2, 3, 4, 5, 6, [7]]

 

 

flat 분석하기

const A = [[1,2,3],[4,[5]], 6];

const R = A.flat(); //[1,2,3,4,[5],6]   (number | number[])[]
const RR = R.flat(); // [1,2,3,4,5,6]   number[]
const RRR = RR.flat(); //[1,2,3,4,5,6]  number[]

const R2 = A.flat(2)    //[1,2,3,4,5,6] number[]
  • A : 3차원 배열
  • R : 2차원 배열
  • RR, RRR : 2차원 배열
  • R2 : 1 차원 배열

 

flat 메서드

flat<A, D extends number = 1>(
        this: A,
        depth?: D,
    ): FlatArray<A, D>[];
  • 매개변수 A, D : 타입
    • A : this 타입 => 원본배열
    • B : 낮출 차원 수
      • 인수가 없으면 기본 1로 설정
  • FlatArray 반환 -> 분석 필요

 

FlatArray

: 컨디셔널 타입을 인덱스 접근 타입으로 나타낸 것

type FlatArray<Arr, Depth extends number> = {
    done: Arr;
    recur: Arr extends ReadonlyArray<infer InnerArr> ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
        : Arr;
}[Depth extends -1 ? "done" : "recur"];
  • Depth
    • -1인 경우 done/ 아니면 recur

Depth에 근거해 위 코드를 쉽게 써보면

//Depth = -1 인 경우
type FlatArray<Arr, -1> = Arr;
// 아닌 경우
type FlatArray<Arr, Depth extends number> = 
    Arr extends ReadonlyArray<infer InnerArr>
     ? FlatArray<InnerArr, [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][Depth]>
        : Arr;

 

(number | (number | number[])[])[] 타입인 3차원 배열에 flat 메서드 호출한 경우

- Arr : 3차원 배열의 타입

- Depth : 1 (Depth: 낮출 차원 수를 의미, 기본값 = 1)

 

1단계 : FlatArray<(number | (number | number[])[])[] , 1> []에서 시작

 

2단계 : Arr extends ReadonlyArray<infer InnerArr> 참 , 거짓 판단

- ReadonlyArray : readonly 가 적용된 배열

     - 모든 배열은 ReadonlyArray를 extends 가능.    ==> 따라서

 

3단계 : infer InnerArr를 통해 InnerArr 배열 추론 가능

- Arr이 (number | (number | number[])[])[]인 경우 

    inferInnerArr = (number | (number | number[])[]

 

[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

: 인덱스 접근 타입

 - Depth가 1이면 0이됨, 21이면 20이됨, 22 이상이면 undefined 됨

====> 차원을 한 단계씩 낮추겠다는 의미 -> Depth 1 줄임

 

따라서 

type FlatArray<(number | (number | number[])[])[], 1> = 
    Arr extends ReadonlyArray<infer InnerArr>   //true
     ? FlatArray<number | (number | number[])[], [0]>
        : Arr;

- FlatArray<number | (number | number[])[], 0>이 됨

 -아직 Depth 가 -1이 안돼 한 번 더 Arr extends ReadonlyArray<infer InnerArr> 필요

 

extends 결과, InnerArr 추론 확인

type GetInner<Arr> =  Arr extends ReadonlyArray<infer InnerArr> 
    ? InnerArr
    : Arr;
type OneDepthInner = GetInner <(number | (number | number[])[])[]>; //number | (number | number[])[]
type TwoDepthInner = GetInner <OneDepthInner>   //number | number[]
  • TwoDepthInner != OneDepthInner.  ===> extends는 true
  • InnerArr = number | number[] ---> Depth가 1 낮아져  FlatArray<number | number[], -1>이 됨

 

 

 

 

 

3.10 promise, awaited 타입 분석하기

규칙1 : Awaited<객체가 아닌 값> === 객체가 아닌 값

규칙2 : Awaited<Promise<T>> === Awaited<T>

 

 

Promise 타입

declare var Promise: PromiseConstructor;
  • Promise는 기존 자바스크립트 객체로 declare를 사용해 타입만 붙여줌
interface PromiseConstructor
  • PromiseConstructor가 실제 Promise객체의 타입으로 new 를 붙여 호출 가능

 

(async() => {
    const str = await Promise.resolve('promise');   //string
    const all = await Promise.all([
        'string',
        Promise.resolve(123),
        Promise.resolve(Promise.resolve(true))
    ])

const chaining = await Promise.resolve('hi')
    .then(() => {
        return 123;
    })
    .then (()=> {
        return true;
    })
    .catch((err) => {
        console.error(err);
    });
})();


const str1 = Promise.resolve('promise');   //Promise<string>
const str2 = await Promise.resolve('promise');   //string
  • resolve 반환값
    • str1 : Promise<Awaited<string>>     --->   Promise<string>
    • str2 : Awaited <Promise<Awaited<string>>>  ---> string

 

Awaited 타입

type Awaited<T> = T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
    T extends object & { then(onfulfilled: infer F, ...args: infer _): any; } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
        F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
            Awaited<V> : // recursively unwrap the value
        never : // the argument to `then` was not callable
    T; // non-object or non-thenable
  • 컨디셔널 3번 중첩
  • 첫 번째
    • T 가 null이나 undefined인지 확인
    • Awaited<null> --> null   /     Awaited<undefined> --> undefined
  • 두 번째
    • T가 object & { then(onfulfilled: infer F, ...args: infer _): any를 extends하는지
    • T가 string, boolean, number 인 경우
      • object가 아니므로 false
      • Awaited<string> --> string / Awaited<boolean> --> boolean / Awaited<number> --> number
      • (규칙1)  Awaited<객체가 아닌 값> === 객체가 아닌 값
        •  Promise<Awaited<string>>
          • Awaited<string> --> string => Promise<string>
    • T가 객체인 경우
      • { then(onfulfilled: infer F, ...args: infer _): any; } 만족 필요
      • Promise 인스턴스는 then 메서드를 가짐
        • Promise 인스턴스 : new Promise() 또는 Promise.resolve()의 반환값
      • then 메서드를 가지므로 Promise 객체는 
        •  object & { then(onfulfilled: infer F, ...args: infer _): any를 extends 함

- Awaited에서 T가 Promise면 then의 첫번째 변수인 F를 infer

  • 세 번째
    • F가 ((value: infer V, ...args: infer _) => any) 를 extends하는지
      • infer하면 -> 첫 번째 매개변수 V를 infer
    • T가 Promise 객체 -> Awaited<T>는 Awaited<V>
      • T : Promise<X>
      • V: X
    • (규칙 2) Awaited<Promise<T>> === Awaited<T>
    • (str2) Awaited<Promise<Awaited<string>>>
      • (규칙1) Awaited<Promise<string>>
      • (규칙2) Awaited<string>
      • (규칙1) string

 

 

-Promise.all 결과물의 타입 추론 알아보기

const all = await Promise.all([
    'string',
    Promise.resolve(123),
    Promise.resolve(Promise.resolve(true)),
]);
export {};

 

 

 

3.11 bind 분석하기

: 함수의 this 값을 영구적으로 설정, 필요할 경우 기본 인자를 미리 지정하여 새로운 함수를 반환

: v5.0.4

const boundFunction = originalFunction.bind(thisArg, ...args);

 

  • thisArg: 바인딩할 this 값, 함수가 호출될 때 this가 항상 thisArg를 가리킴
  • args (선택적): 새로운 함수가 호출될 때 미리 전달할 인자들
  • 반환값: this 값이 고정된 새로운 함수를 반환

 

function a(this : Window | Document) {
    return this;
}

const b =a.bind(document);
const c = b();

 

 

- bind 메서드 타입을 확인해 보면 bind 함수의 오버로딩이 13개 존재

 

  • CallableFunction : 호출할 수 있는 함수
  • NewableFunction : new를 붙여 호출할 수 있는 함수 (즉, 클래스)

 

오버로딩이 많이 필요한 이유 

function add(a =0, b =0 , c =0, d =0, e =0) {
    return a + b + c +d + e ;
}

const add0 = add.bind(null);
const add1 = add.bind(null,1);
const add2 = add.bind(null,1,2);
const add3 = add.bind(null,1,2,3);
const add4 = add.bind(null,1,2,3,4);
const add5 = add.bind(null,1,2,3,4,5);

 

add0 함수

: 인수 1개

const add0 = add.bind(null);
//호출
add0(1,2,3,4,5);
    bind<T>(this: T, thisArg: ThisParameterType<T>): OmitThisParameter<T>;
  • this인 T 
    • (a? :number , b? :number , c? : number, d? : number, e ? : number) => number
  • ThisParameterType<T> 
    • T가 함수면 this를 infer해 가져옴
    • infer할 수 없으면 unknown
  • OmitThisParameter<T>
    • ThisParameterType이 unknown되면 T가 됨
    • ThisParameterType가 (...args : infer A) => infer R 꼴 함수면
      •  (...args : A) => R 꼴 함수가 됨
    •  this 타입이 존재하면 : ThisParameterType = unknown 
      • OmitThisParameter은 기존 함수에서 this 타입을 제거한 함수

왜 this를 제거?

interface CallableFunction {
    myBind<T> (this : T, thisArg : ThisParameterType<T>) : T;
}

function myAdd(this: number, a =0, b=0 ) {
    return this + a + b;
}

const myAdd0 = myAdd.myBind(5);
myAdd0(3,4);    //에러  this가 void
  • myBind : OmitThisParameter을 사용하지 않는 임의의 메서드, bind와는 반환값 타입만 다름
  • 이미 myBind로 this에 5 지정
  • 후에 myAdd0 함수에서는 this 필요 없음

 

add1 함수

const add1= add.bind(null,1);
add1(2,3,4,5);
add1(2,3,4,5.6);
  • 인수 5개 -> 에러
  • 어떻게 인수의 갯수를 알 수 있음?
    bind<T, A0, A extends any[], R>(this: (this: T, ...arg0:A0, ...args: A) => R, thisArg: T, arg0: A0): (...args: A) => R;
  • T(thisArg) = null, A0(arg0) = 1
    • this    =.     (this: T, ...arg0:A0, ...args: A) => R
  • A = [b? : number | undefined, c? : number | undefined, d? : number | undefined, e? : number | undefined]
    • 첫번째 매개변수를 제외한 나머지 매개변수의 튜플
  • R = number
  • 반환값 타입 : (...args: A) => R
  • (최종) add1 함수
    • (b? : number | undefined, c? : number | undefined, d? : number | undefined, e? : number | undefined) => number

- 이런식으로 add2(3), add3(2), add4(1) 인수의 개수를 줄임

 

 

 

'typescript' 카테고리의 다른 글

lib.es5.d.ts 분석하기 - 3.1 ~ 3.6  (2) 2024.11.03
2.28 ~ 2.32  (2) 2024.10.20
2.23 ~ 2.27  (0) 2024.10.13
2.18 ~ 2.22  (1) 2024.10.06
타입스크립트 2.13 ~ 2.17  (0) 2024.09.28