본문 바로가기

MATHrone/STUDY

TypeScript - 제네릭, 유틸리티 타입

728x90

 

Generic 
<T>

클래스, 함수, 인터페이스를 다양한 타입으로 재사용 가능

선언할 때는 타입파라미터만 적어주고, 생성하는 시점에 타입 파라미터를 결정 

코드를 재사용하는데 아주 유용하게 사용된다

 

 

함수
function getSize(arr) : number {
	return arr.length;
}


const arr1 = [1,2,3]; // 매개변수가 숫자 배열
getSize(arr1); //3

const arr2 = ["a","b","c"];//매개 변수가 문자열 배열
getSize(arr2);

다음과 같이 매개변수의 타입이 다른 경우

 

함수 오버로드 와 유니온 타입으로 해결 

//함수 오버로드 + 유니온 타입
function getSize(arr :number[]): number;
fucntion getSize(arr : string[]: number;

function getSize(arr : number[] | string[]) : number {
	return arr.length;
}

만약 계속 다른 타입이 늘어나게 된다면, 코드가 길어진다. 

이것을 간단하게 제네릭으로 해결할 수 있다. 

 

제네릭으로 해결

function getSize<T>(arr: T[]) : number { //type을 의미하는 <T>를 사용, T타입의 배열이 매개변수임을 작성
	return arr.length;
}


const arr1 = [1,2,3]; // 매개변수가 숫자 배열
getSize<number>(arr1); // 사용하는 쪽에서 타입을 명시함

const arr2 = ["a","b","c"];//매개 변수가 문자열 배열
getSize<string>(arr2);

const arr3 = [false, false, true];//매개 변수가 boolean 배열 
getSize<boolean>(arr3);

다음과 같이 <T>를 이용하여 함수를 정의한 후 해당 타입을 사용해서 매개변수의 타입을 임시로 지정해준다.

그 후 사용시점에 타입을 결정해준다. 

 

 

 

인터페이스

인터페이스의 멤버변수 중 하나의 타입이 결정되지 않은 경우도 비슷하게 활용할 수 있다.

interface Mobile<T> {
	name:string;
    	price: number;
    	option: T; //타입이 결정되지 않은 멤버변수 
}

const m1: Mobile<object>{
	name: "s21",
    	price: 1000,
    	option: { //object타입이 들어옴
    		color: "red",
        	coupon: false,
    	},
 };

//참고로 object에 형식이 정해진 경우 이렇게도 가능하다
 const m1: Mobile<{color : string; coupon:boolean}>{
	name: "s21",
    	price: 1000,
    	option: { //object타입이 들어옴
    		color: "red",
       		coupon: false,
    	},
 };
 
 
 const m2: Mobile<string>{
	name: "s21",
    	price: 1000,
    	option: "good", //string타입이 들어옴 
 };

 

제네릭에서의 extends의 활용

또한,  extends를 이용하여, 입력으로 들어올 특정 타입T의 매개변수에 멤버변수등을 지정해줄 수 있다.

interface User {
	name: string;
	age:number;
}

interface Car {
	name: string;
	color : string;
}

interface Book {
	price:number;
}

const user: User = {name: "Andrew", age:39};
const car: Car = {name: "bmw", color:"red"};
const book: Book = {price: 3000};

function showName<T extends {name: string}> (data:T): string{
	return data.name;
}

showName(user);
showName(car);
showName(book); //book이라는 객체는 name을 가지고 있지 않으므로 에러

showName이라는 함수에 매개변수로 들어올 변수 data의 타입은 T 인데,

이 T타입은 name: string이라는 멤버를 보유하고 있어야 한다는 의미이다.

다양한 타입을 가진 객체가 매개변수로 올 수 있지만, name(string인)이라는 멤버를 보유하고 있는 객체만 올 수 있게된다.

 

 

유틸리티 타입
Utility Type

 

keyof

해당 타입이 가진 모든 멤버변수의 키값을 의미한다.

interface User{
	id: number;
    	name: string;
    	age: number;
    	gender: "m"|"f";
}


type UserKey = keyof User; //'id' | 'name' | 'age' | 'gender' 와 같은 의미이다.

const uk:Userkey = "grade"; //error 존재하지 않는 멤버 키이므로
const uk2:Userkey = "id"; //ok

 

 

Partial<T>

프로퍼티를 전부 optional로 변경해준다.

interface User{
	id: number;
    	name: string;
    	age: number;
    	gender: "m"|"f";
}

 
let admin: Partial<User> = { //age, gender가 빠졌지만 Partial<User>이므로 오류가 아님
	id: 1,
    	name: "Bob",
}

 

즉 admin객체는 다음과 같은 인터페이스의 객체와 같다 

interface User{
	id?: number;
    	name?: string;
    	age?: number;
    	gender?: "m"|"f";
}

 

 

 

Required<T>

모든 프로퍼티를 필수로 변경해준다.

interface User{
	id: number;
    	name: string;
    	age?: number;
    	gender?: "m"|"f";
}

 
let admin: Required<User> = { //age, gender도 필수가 되어 입력해주지 않으면 에러
	id: 1,
    	name: "Bob",
        age: 10,
        gender : "f",
}

 

 

Readonly<T> 

읽기 전용으로 변경하여, 처음 생성시를 제외하고는 수정이 불가능하다. 

interface User{
	id: number;
    	name: string;
    	age: number;
    	gender: "m"|"f";
}

let admin: Readonly<User> = {
	id: 1,
    name: "Shen",
};

let admin2: User = {
	id: 2,
    name: "Zed",
};

admin.name = "akali"; //error 
admin2.name = "kenen"; //ok

 

 

 

Record<K,T>

 key와 value 쌍을 지정할 수 있다. 

 

학생의 학년별(1~4) 점수(A ~ F) 를 저장하는 오브젝트를 생성해본다.

interface Score {
	"1" : "A" | "B" | "C" | "D" | "F";
	"2" : "A" | "B" | "C" | "D" | "F";
	"3" : "A" | "B" | "C" | "D" | "F";
	"4" : "A" | "B" | "C" | "D" | "F";
}

const yjScore: Score = {
	1: "B",
   	2: "B",
    	3: "A",
    	4: "A",
};

Record를 사용하지 않으면 다음과 같이 작성해야 한다.

인터페이스를 만들지 않고, Record를 이용해서 <key: 학년, value: 점수> 쌍으로 만들어보면,

 

const yjScore: Record<"1" | "2" | "3" | "4", "A" | "B" | "C" | "D" | "F"> = {
	1: "B",
   	2: "B",
    	3: "A",
    	4: "A",
};

타입을 사용해서 조금 더 간단하게 정리해보면, 

 

type Grade = "1" | "2" | "3" | "4";
type Score = "A" | "B" | "C" | "D" | "F";


const yjScore: Record<Grade, Score> = {
	1: "B",
   	2: "B",
    	3: "A",
    	4: "A",
};

 

 

 

또한, 함수에도 이용할 수 있으며, 다음과 같이 응용해 볼 수 있다. 

interface User {
	id: number;
    	name: string;
    	age: number;
}


//해당 유저가 유효한 유저인지 확인하여 결과 object를 반환하는 함수
function isValid(user: User) {
	const result: Record<keyof User, boolean> = {//User의 멤버 키 값, 유효한지(boolean)
    	id: user.id > 0,
        name: user.name !== "", 
        age: user.age > 0,
    };
    return result;
}

 

 

 

Pick<T, K>

T 타입에서 K property만 골라서 가지고 있는 타입을 의미한다.

interface User {
	id: number;
    	name: string;
    	age: number;
    	gender: "M" | "W";
}

const admin: Pick<User, "id" | "name"> = { //User 인터페이스에서 id, name멤버 만을 가진 타입
	id: 0,
    	name: "Bob",
};

 

 

Omit<T,K>

Pick과 반대로 K 프로터피만을 제외하고 가진 타입을 말한다.

interface User {
	id: number;
    	name: string;
    	age: number;
    	gender: "M" | "W";
}

const admin: Omit<User, "id" | "name"> = { //User 인터페이스에서 id, name멤버를 제외한 타입
	age: 10,
    	gender : "M",
};

 

 

Exclude<T1, T2>

T1타입에서 T2"타입"과 겹치는 것을 제거하는 방식으로 Omit은 "프로퍼티"를 제외하므로 차이점이 있다. 

type T1 = string | number | boolean;
type T2 = Exclude<T1, number | string>; // T2는 boolean만을 가짐

 

 

NonNullable<Type>

Null과 undefined을 제외한 타입

type T1 = string | null | undefined | void;
type T2 = NonNullable<T1>; //stirng, void만 가능

 

 

 

 

참고영상 

https://www.youtube.com/watch?v=IeXZo-JXJjc&list=PLZKTXPmaJk8KhKQ_BILr1JKCJbR0EGlx0&index=8 

 

 

 

728x90