[Typescript] 타입 분석하기 - 1

장택진·2023년 8월 4일
0

TypeScript

목록 보기
3/3
post-thumbnail

forEach, map 제네릭 분석

function add<T>(x:T, y:T) : T {return x}

add(1,2); // 자동으로 T의 타입을 number로 추론해줌.
add<number>(1,2); // 타입스크립트가 추론을 잘못해줄 때 지정해줘야됨.

  • filter
# 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}
  • map
# 제네릭 부분에 (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());

filter 제네릭 분석

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를 똑같이 가져와서 타입을 좁혀준다.

forEach 타입 직접 만들기

  • 나쁜 예
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);
});

map 타입 직접 만들기

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로 타입을 잘 지정해준다.

filter 타입 직접 만들기

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 타입가드로 타입을 좁혀준다. 

공변성과 반공변성

  • return 값
  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로 지정해준다.

profile
필요한 것은 노력과 선택과 치킨

2개의 댓글

comment-user-thumbnail
2023년 8월 4일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

1개의 답글