과거 프로그래밍 자료들/Javascript&typescript

[TS] forEach, map, filter 제네릭 분석

평부 2022. 9. 16. 16:52

 

출처 : https://github.com/ZeroCho/ts-all-in-one

 

GitHub - ZeroCho/ts-all-in-one

Contribute to ZeroCho/ts-all-in-one development by creating an account on GitHub.

github.com

 

* forEach, map, filter에 대한 interface<T> 부분

https://github.com/microsoft/TypeScript/blob/main/lib/lib.es5.d.ts

 

GitHub - microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

TypeScript is a superset of JavaScript that compiles to clean JavaScript output. - GitHub - microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

github.com

 

 

제네릭에 대해 타입을 변수처럼 만든다는 것은 알겠음 

▶ 실제로는 어떻게 이용되는 것인가?

 

 

* forEach

//forEach
[1,2,3].forEach((item) => {console.log(item)}); //1 2 3

//item : number로 표시됨
[1,2,3].forEach((item) => {console.log(item)});
//item : string로 표시됨
['1','2','3'].forEach((item) => {console.log(item)});
//item: boolean으로 표시됨
[true,false,true].forEach((item) => {console.log(item)})

//위의 forEach 예제들의 item의 값들이 저렇게 표시되는 이유 : 제네릭 덕분(JS에서는 사라짐)

interface Array<T> {//T자리에 뭐가 올지 모름(자리만 만듦)
    forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg? :any): void
}//하나가 정해지면 나머지도 숫자, 문자열로 따라감

//콜백함수 : callbackfn: (value: T, index: number, array: T[]) => void  == 위의 forEach 예제들의 (item) => {console.log(item)}
//value = 위의 forEach 예제들의 item

 

 

* 그런데 꼭 제네릭을 써야 하나? 그 자리에 해당되는 값들만 넣어주면 되지 않나?

▶ 그럴 경우 생기는 문제 : 여러 타입이 존재 시 같은 타입들만 사용하는 게 아니라 다른 타입인데도 같이 사용되는 오류들 발생

 

function add(x: string | number, y: string | number) {}
add(1, '2') //오류도 맞다고 표시됨
add('1', 2) //오류도 맞다고 표시됨

//위의 사례를 막고 타입이 일치하는 것들만 가능하도록 하게 함 = 제네릭 사용
add(1, 2)
add('1', '2')
add(true, false)

//여러 T 중 하나라도 타입이 정해지면 다른 것들도 동일하다고 생각함
function add<T>(x: T, y: T): T {return x}

add(1, 2) //x가 number면 y도 number로 생각함
//타입 파라미터 : add<number>(1, 2)도 가능함
add('1', '2') //x가 string이면 y도 string으로 생각

 

 

* map

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

//const strings = string[] 추론
//예시
const strings = [1, 2, 3].map((item) => item.toString())

//위의 Array<T> = 예시의 number, 위의 U = 예시의 item.toString() => string값
//따라서 map 함수의 전체 리턴값 string[]

 

* filter

interface Array<T> {
    filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
    filter(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): T[];
}

//const filtered = number[]
const filtered = [1, 2, 3, 4, 5].filter((value) => value %2);

//T는 number, S도 number
//value % 2 = number
//filtered는 number[]

//문제
//string[]이어야 하는데 (string | number)[]로 나옴
const filtered2 = ['1', 2, '3', 4, '5'].filter((value) => typeof value === "string");
//T는 string | number, S는 string | number 결과도 string | number

//string[]만 나오게 하려면?
//(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any)
const predicate = (value: string | number): value is string => typeof value === "string"; 
const filtered3 = ['1', 2, '3', 4, '5'].filter(predicate);

////number[]만 나오게 하려면?
const checkNum = (value: string | number): value is number => typeof value === "number";
const filtered4 = ['1', 2, '3', 4, '5'].filter(checkNum);