function add<T>(x:T, y:T) : T {return x}
add(1,2); // 자동으로 T의 타입을 number로 추론해줌.
add<number>(1,2); // 타입스크립트가 추론을 잘못해줄 때 지정해줘야됨.
# interface 안에서도 제너릭사용가능
interface Array<T> {
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
}
# 제네릭이 타입추론 제대로 해줌
// (parameter) item: number
[1, 2, 3].forEach((value) => { console.log(value); });
// (parameter) item: string
['1', '2', '3'].forEach((value) => { console.log(value); });
// (parameter) item: string | number | boolean
['123', 123, true].forEach((value) => { console.log(value); });
# 제너릭을 사용하지않고 가능성있는 타입을 적어주면?
function add(x: string | number, y: string | number) {
}
// 이런 함수가 가능하게됨
add('1', 2);
add(1, '2');
// 제네릭을 사용하면 위와같은 케이스들을 잘 걸러준다.
function add<T>(x: T, y: T) {}
add<number>(1, 2); // 제네릭 타입 파라미터가 뭔지 적어줄수도 있음
add('1', '2')
add(true, false);
function add<T>(x: T, y: T): T {return x}
# 제네릭 부분에 (T, U) 값을 직접 적어주면 덜 헷갈림
interface Array<T> {
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}
// ['1', '2', '3'] string[]
const strings = [1, 2, 3].map((item) => item.toString());
interface Array<T> {
filter<S extends T>(predicate: (value : number, index:number, array:T[]) => value is S ,thisArg?:any) : S;
filter(predicate : (value : T, index:T, array: T[]) => unknown, thisArg?: any): T[]
}
const filtered = [1,2,3,4,5].filter((v) => v % 2);
// 위의 filtered는 둘중 어느것에 더 적합할까?
// v가 number이기 때문에 v % 2가 unknown은 아닐 것임.
// 결론적으로 첫번째를 따를 것이다.
const filter2 = ['1',2,'3',4,'5'].filter((v) => typeof v === 'string');
// filter2를 number | string 으로 타입을 추론함.
interface Array<T> {
filter<S extends number | string>(predicate: (value : number | string, index:number | string, array:number | string[]) => value is S ,thisArg?:any) : (number | string)[];
filter(predicate : (value : T, index:number, array: T[]) => unknown, thisArg?: any): T[]
}
// 둘 다 어짜피 string | number
첫 번째 filter의 S는 string | number이기 때문에 string이 될 가능성이 남아있지만, 두 번째 filter의 T는 고정되어 있기 때문에 타입이 바뀔 가능성이 존재하지 않는다. 타입 추론을 제대로 해줄 수 없음
interface Array<T> {
filter<S extends T>(predicate: (value : T, index:T, array:T[]) => value is S ,thisArg?:any) : S[];
filter(predicate : (value : T, index:number, array: T[]) => unknown, thisArg?: any): T[]
}
const predicate = (value: string | number) : value is string => typeof value === 'string';
// T는 string | number, S는 string으로 타입을 좁혀서 추론하게 한다.
const filter2 = ['1',2,'3',4,'5'].filter((v) => typeof v === 'string');
predicate를 똑같이 가져와서 타입을 좁혀준다.
interface Arr {
forEach(callback : (item: number | string) => void) : void;
}
const a : Arr = [1,2,3];
a.forEach((item) => {
console.log(item);
});
const b : Arr = ['1','2','3'];
b.forEach((item) => {
console.log(item);
item.charAt(3); // 할경우 에러가 난다. item의 타입을 잘못 선언한것.
});
interface Arr<T> {
forEach(callback : (item: T) => void) : void;
}
// 현재는 index에 관한 파라미터가 없지만 사용하려면 넣어주면 된다.
const a : Arr<number> = [1,2,3];
a.forEach((item) => {
console.log(item);
});
interface Arr<T> {
forEach(callback : (item: T) => void) : void;
// map(callback: (v) => void): void
//map(callback: (v: T) => T): T[] -> return 값에 toString이 있을 시 오류
# 새로운 타입 S를 도입
map<S>(callback: (v: T)=> S): S[];
map<S>
}
const a:Arr<number> = [1,2,3];
const b = a.map((v) => v+1); // [2,3,4]
const c = a.map((v) => v.toString());
Arr 을 입력하면 T의 타입이 number가 된다. 따라서 map 콜백함수의 v에도 타입을 T로 지정해줘야 타입스크립트가 any에서 number로 타입을 잘 지정해준다.
interface Arr<T> {
//filter(callback: (v:T)=> boolean) : T[];
filter<S extends T>(callback: (v:T)=> v is S) : S[];
}
// extends를 사용하지 않을 경우
// S는 T의 부분집합이라서 T가 S로 좁혀질 수 있다.
const a:Arr<number> = [1,2,3];
const b = a.filter((v) => v % 2 === 0); // [2] number[]
const c: Arr<number|string> = [1,'2',3,'4',5];
//const d = c.filter((v) => typeof v === 'string');
// 타입을 (string|numebr)[] 로 추론한다.
const d = c.filter((v): v is string => typeof v === 'string');
// is 타입가드로 타입을 좁혀준다.
function a(x:string) : number {
return +x;
}
type B = (x:string) => number | string;
const b:B = a;
// a는 number로 반환, B는 number or string으로 반환.
//서로 타입이 다른데 가능할까? 라는 의문이 생긴다.
// -> return 값은 더 넓은 타입으로 대입이 가능하다.
function a(x:string) : number | string{
return +x;
}
type B = (x:string) => number;
const b:B = a;
// return 값이 넓은 타입에서 좁은 타입으로는 대입이 불가능하다.
// x
function a(x: number | string) : number{
return +x;
}
type B = (x:string) => number;
const b:B = a;
//매개변수 같은 경우는 좁은 타입으로 대입된다.
제일 중요한 것
- 매개변수 : 넓은 타입에서 좁은 타입으로 대입 가능
- return 값 : 좁은 타입에서 넓은 타입으로 대입 가능
오버로딩이란? -> 같은 타입을 여러번 선언하는것
function add(x:number, y:number): number
function add(x:string, y:string) : string
function add(x: number | string, y: number| string){
return x+y;
}
declare 란? -> 타입만 지정해주고 바디부분은 구현하지 않아도 다른 곳에 있다고 생각한다.
declare function add(x:number, y:number, z?:number): number
err의 타입은 unknown으로 지정.
interface CustomError {
name : string;
message: string;
stack?: string;
response?: {
date:any;
}
}
declare const axios: Axios;
(async () => {
try {
await axios.get();
} catch (err: unknown) {
console.error((err as CustomError).response?.data);
// err.response?.data -> 알 수 없는 형식이라고 뜬다. 일회성이기 때문에.
// const customError = err as CustomError 처럼 변수 지정을 해줘야 재사용 가능
}
})();
(async () => {
try {
await axios.get();
} catch (err: unknown) {
if (err instanceof typeof CustomError) {
const CustomError = err as CustomError;
console.error(CustomError.response?.data);
CustomError.data;
}
}
})();
// interface로 지정할 경우 에러가 난다.
매번 (err as CustomError)을 지정해주거나 변수에 err as CustomError를 지정해준다.
class CustomError extends Error{
response?: {
data: any;
}
}
(async () => {
try {
await axios.get();
} catch (err: unknown) {
if (err instanceof CustomError) {
const CustomError = err as CustomError;
console.error(CustomError.response?.data);
CustomError.data;
}
}
interface를 사용할 경우 instanceof를 쓸 수 없음. ( 타입가드로 사용할 수 없음. )
js에 남아있으면서도 타입 가드가 가능한 class로 지정해준다.
개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.