forEach
우선 forEach 부터 시작해봅시다.
Arr 타입에 대한 forEach 메소드의 타입을 정의해주어야합니다.
interface Arr {}
const a: Arr = [1, 2, 3];
a.forEach((item) => {
console.log(item);
});
a.forEach((item) => {
console.log(item);
return 3;
});
우선 forEach는 리턴값이 없기때문에 void로 정의해줍니다. 이 함수는 아무것도 반환하지 않는다는 의미입니다.
interface Arr {
forEach(): void;
}
그 다음으로는 forEach 내부의 callback 의 타입을 지정해주어야합니다.
우선 callback 또한 리턴을 void 로 선언했습니다. 콜백의 리턴이 void 인것은 후에 수정을 해줘야할 것 같습니다.
interface Arr {
forEach(callback: () => void): void;
}
위와 같이 타입을 정의해주면 아래와 같은 에러메세지를 볼 수 있었습니다.
콜백안의 파라미터의 타입을 정의해주라고 합니다.
다음과 같이 콜백안의 파라미터의 타입을 정의해주었습니다. 우선 number로 정의했습니다.
interface Arr {
forEach(callback: (item: number) => void): void;
}
하지만 위와 같이 타입을 정해준다면 item이 number 인 경우밖에 커버할 수 없습니다.
이럴때 사용해야하는 것이 제네릭입니다.
제네릭을 넣어줘야하는 위치 interface Arr 뒤 입니다. 다음과 같습니다.
- 타입 바로 뒤
interface Arr<T> { forEach(callback: (item: T) => void): void; }
이미 js 메소드에 대한 타입이 정리된 d.ts 파일 을 보던중에 thisArg 라는 파라미터가 뭔지 궁금했습니다.
곰곰히 생각해본 결과 이전에 eventListener 와 같은 코드를 콜백으로 넣을경우 this 가 window 일때 원하던대로 동작하지 않던 경우가 떠올랐습니다. 그것에 대한 옵션인것 같습니다.
forEach(
callbackfn: (value: T, index: number, array: T[]) => void,
thisArg?: any
): void;
어렵다고 생각했는데 별거 없는 것 같습니다.
map
interface Arr<T> {
forEach(callback: (item: T, index: number, array: T[]) => void): void;
map(callback: (el: T) => void): void;
}
const a: Arr<number> = [1, 2, 3];
const b = a.map((el) => el + 1);
console.log(b); // 잘 나오지만 b 의 타입이 void 였다.
아래 처럼 코드르 수정해야 b 의 타입이 void 가 아닌 number[] 가 됩니다.
interface Arr<T> {
forEach(callback: (item: T, index: number, array: T[]) => void): void;
map(callback: (el: T) => T): T[];
}
const a: Arr<number> = [1, 2, 3];
const b = a.map((el) => el + 1);
console.log(b); // [1,2,3]
그런데 다음처럼 element 를 string으로 변경할 경우 타입의 문제가 생깁니다.
왜냐하면 콜백의 반환이 T 제네릭으로 선언되어있습니다. 따라서 현재 number type에 elements 에 toString 메소드를 적용하려니 에러가 납니다.
이런 경우에는 제네릭을 하나더 써주어야합니다.
다음과 같이 말이죠.
<S> 라는 제네릭을 map 뒤에 추가해줌으로써 좀 더 유연한 타입정의가 가능하게 되었습니다.
동작 순서를 설명하면 다음과 같습니다.
- el.toString()이 string 타입이므로 S = string 이 됩니다.
- 나머지 S에 string 이 정의되어 map<S> , 함수의 반환도 string[] 가 됩니다.
interface Arr<T> {
forEach(callback: (item: T, index: number, array: T[]) => void): void;
map<S>(callback: (el: T) => S): S[];
}
const a: Arr<number> = [1, 2, 3];
const b = a.map((el) => el + 1);
const c = a.map((el) => el.toString());
filter
우선 기본 세팅은 다음 처럼 됩니다.
interface Arr<T> {
filter(callback: (el: T) => void): void;
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const b = a.filter((el) => typeof el === "string");
filter 타입은 다음과 같습니다.
음 예상 되는 결과는 string[] 이여하는데 b는 void 타입인 것을 확인했습니다.
이렇게 바꿔줘 봤습니다.
interface Arr<T> {
filter(callback: (el: T) => boolean): T[];
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const b = a.filter((el) => typeof el === "string");
타입은 다음과 같네요.
하나의 제네릭 가지고는 string[] 을 만들 수가 없습니다. 따라서 U 라는 제네릭을 추가합니다.
저는 이 과정에서 다음과 같은 타입스크립트 타이핑으로 완료가 되었다 라고 생각했습니다.
interface Arr<T> {
forEach(callback: (item: T, index: number, array: T[]) => void): void;
map<S>(callback: (el: T) => S): S[];
filter<U>(callback: (el: T) => boolean): U[];
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const b = a.filter<string>((el) => typeof el === "string");
const c = a.filter<number>((el) => typeof el === "number");
console.log(b);
console.log(c);
하지만 lib.es5.d.ts 를 보니까 다음처럼 타이핑이 되었더라구요. 일단 아래 코드를 분석해보겠습니다.
우선 string | number 라는 범위를 좁히기 위해서 커스텀 타입 가드를 사용한 모습입니다.
그래서 저도 타입가드를 사용하기 위해 제 코드를 커스텀 타입가드를 사용하는 상태로 바꾸어보았습니다.
interface Arr<T> {
filter<U>(callback: (el: T) => el is U): U[];
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const b = a.filter<string>((el) => typeof el === "string");
const c = a.filter<number>((el) => typeof el === "number");
console.log(b);
console.log(c);
다음과 같은 에러를 만났습니다.
제네릭 U 와 제네릭 T 가 아무런 연관이 없다고 합니다.
범위를 좁힌다는 것은 두 제네릭 간의 어떤 연관 관계 가 있을때 교집합으로 범위를 좁힌다는 뜻인데
전혀 교점이 없다는 뜻입니다.
따라서 extends 를 사용해서 두 제네릭 간의 연관관계를 만들어줍니다.
다음 처럼 말이죠. 이해하시기 쉽게 밴다이어그램을 그려서 표현해보겠습니다.
filter<U extends T>(callback: (el: T) => el is U): U[];
T 는 string 과 number 타입이 유니온으로 묶여있기 때문에 위 처럼 밴다이어그램이 그려질 겁니다.
U 는 저 밴다이어그램의 부분집합 어딘가가 되겠습니다. string , number 가 될 수 있겠네요.
interface Arr<T> {
filter<U>(callback: (el: T) => el is U): U[];
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const b = a.filter<string>((el) => typeof el === "string");
const c = a.filter<number>((el) => typeof el === "number");
console.log(b);
console.log(c);
위와 같이 타이핑을 했는데 다음 에러를 만났습니다.
signature로 만든 커스텀 타입가드를 함수 리턴에 사용하라는 것이네요.
다음처럼 커스텀 타입가드를 적용해주었습니다.
커스텀 타입가드의 역할은 큰 범위의 타입 값을 좁은 범위로 좁혀주는 것입니다.
const b = a.filter((el): el is string => typeof el === "string");
const c = a.filter((el): el is number => typeof el === "number");
filter 안의 타입을 다음처럼 빼줄 수도 있습니다.
interface Arr<T> {
filter<U extends T>(callback: (el: T) => el is U): U[];
}
const a: Arr<string | number> = [1, "2", 2, "3", 3];
const predicate = (v: string | number): v is number => typeof v === "number";
const c = a.filter(predicate);
console.log(c);
predicate 로 뺀 함수는 커스텀 타입이 number 로 하드 코딩이 되어있는 상태여서, 조금더 유연하게 끔 변수를 할당을 하고 싶었습니다. 따라서 predicate 변수를 return 하는 함수를 만들었습니다.
정리를 하면 다음과 같습니다.
- 커스텀 타입가드를 사용해서 큰 범위의 타입값을 좁은 범위로 좁혀줄 수 있다.
- 제네릭을 두개 사용할 경우 관계를 엮기 위해 extends 를 사용한다.
참고자료
https://developer-talk.tistory.com/359
[[TypeScript]함수 타입(Function Type)
TypeScript에서 함수 정의 TypeScript의 함수는 JavaScript처럼 함수를 생성할 수 있지만, 매개변수의 타입과 반환 타입을 설정해야 합니다. 다음은 number 타입의 매개변수와 string 값을 반환하는 getParam()..
developer-talk.tistory.com](https://developer-talk.tistory.com/359)
'공부기록 > 웹 개발' 카테고리의 다른 글
[타입스크립트] Utility Types (0) | 2022.10.11 |
---|---|
[타입스크립트] keyof , in keyof (0) | 2022.10.06 |
[타입스크립트] class (0) | 2022.10.05 |
자주 사용하는 로더 (1) | 2022.09.30 |
원격 서버에 리액트 프로젝트 정적 배포한 과정 (1) | 2022.09.25 |