목차
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>
- Promise<Awaited<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
- F가 ((value: infer V, ...args: infer _) => any) 를 extends하는지
-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 |