오버로딩 흉내내기
ts,js에서는 오버로딩을 지원하지 않는다.
오버로딩이란 같은 메소드 명이라도 변수의 타입, 변수 길이에 따라 다르게 구현하는 것
일단 코드를 보자
function shuffle (value: string | any[]): string | any[] {
if (typeof value === 'string') {
return value
.split('')
.sort(() => Math.random() - 0.5)
.join('');
}
return value.sort(() => Math.random() - 0.5);
}
const a1 = shuffle('hi mart') // string | any[]
const a2 = shuffle(['a','b','c','d','e']); // string | any[]
const a3 = shuffle([1,2,3,4,5]); // string | any[]
이 shuffle 함수는 string | 배열을 반환하는 함수이다.
오버로딩을 활용하여 좀 더 엄격한 타입을 리턴받을 수 있다.
function shuffle (value: string):string;
function shuffle<T>(value:T[]):T[];
function shuffle (value: string | any[]): string | any[] {
if (typeof value === 'string') {
return value
.split('')
.sort(() => Math.random() - 0.5)
.join('');
}
return value.sort(() => Math.random() - 0.5);
}
const a1 = shuffle('hi mart') // string
const a2 = shuffle(['a','b','c','d','e']); // string[]
const a3 = shuffle([1,2,3,4,5]); // number[]
보통 다른 언어라면 오버로딩을 구현할 때 각각 메소드마다 다른 로직을 구현해줘야 한다.
하지만 ts에서는 저렇게 위에 함수를 선언해주기만 하면 된다.
참고로 함수 선언식에서만 오버로딩이 가능하고 화살표 함수로 작성된 형태는 불가능하다.
클래스에서 오버로딩은 다음과 같이 작성할 수 있다.
class Modal {
public openComponent(id:string, components: string[]): void;
public openComponent(components: string[]): void;
public openComponent(idOrComponents: string | string[], components?: string[]): void {
if(typeof idOrComponents === 'string'){
if(components !== undefined){
idOrComponents; // string
components; // string[]
}
}
else if(typeof idOrComponents === 'object'){
if(components !== undefined){
idOrComponents; // string []
components; // string[]
}
}
if(components === undefined){
idOrComponents; // string | string[]
}
}
}
const modal = new Modal();
modal.openComponent('id',['id1', 'id2']);
openComponent 라는 함수를 오버로딩 했다. 구현 로직에서
첫번째 파라미터인 idOrComponents 는 반드시 위 오버로딩한 함수 선언부 첫번쨰 파라미터의 타입들을
유니온 타입으로 다 받아들여야 한다.
안그러면 오류
Mapped Type
interface Person{
name:string;
age:number;
}
type ReadonlyPerson = Readonly<Person>;
const person: ReadonlyPerson = Object.freeze<Person>({
name: 'king',
age:2
});
person.name = '.'; // error
person.age = 23; // error
Readonly<generic> 타입은 제너릭에 들어온 타입을 모두 readonly 키워드로 바꿔준다. 즉
다음과 동일하다.
interface Person{
readonly name:string;
readonly age:number;
}
다음 코드를 보자.
interface Person {
name: string;
age: number;
}
type Nullable<T> = {
[P in keyof T]: T[P] | null;
}
type Stringify<T> = {
[P in keyof T]: string;
}
type PartialNullablePerson = Partial<Nullable<Stringify<Person>>>;
let p: PartialNullablePerson;
p = { name: 'king', age: '2'};
p = { name: 'king' };
p = { name: undefined , age: null};
먼저 Nullable 을 살펴보자 제너릭을 받아 제너릭의 키 속성을 순회하면서 null 과 함께 유니온 타입으로 명시해준다.
즉 기존 제너릭 객체 타입에 null 타입을 추가해주는 것.
Stringify 는 말그대로 제너릭으로 들어온 속성 타입을 다 string으로 바꿔주는 것이다.
type PartialNullablePerson = Partial<Nullable<Stringify<Person>>>;
그럼 위 타입은 설명하자면 제너릭으로 들어온 타입을 모두 string 으로 변환하고 null 과 유니온 타입이 되고
모든 속성이 옵셔너블 해진다. 다음과 같다.
{
name? : string | null | undefined;
age? : string | null | undefined;
}
다음은 공식 DOC에서 내장되어있는 유틸리티 타입이다 내부 구현은 다음과 같이 이루어져 있다.
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
신기하다.
readonly <T>
interface Book {
title: string;
author: string;
}
interface RootState {
book: {
books:Book[];
}
}
type ReadonlyRootState = Readonly<RootState>;
let state: ReadonlyRootState = {} as ReadonlyRootState;
const book1 = state.book.books[0];
book1.title = 'ssssss'; // 띠용? set이 가능해짐
위 코드는 RootState 인터페이스의 모든 속성을 읽기 전용으로 바꾸었지만
book1.title 의 set이 가능해지는 상황이다.
당연한것이 RootState의 book 프로퍼티만 읽기 전용일뿐 book 의 프로퍼티는 읽기 전용이 아니기 때문이다.
그래서 재귀적으로 객체의 모든 속성을 읽기 전용으로 만들어야 한다.
공식 문서를 뒤져본 결과 이러한 유틸리티 타입은 아직 없는 것 같다. 직접 구현해야 한다.
DeepReadonly<T>
type DeepReadonly<T> = T extends (infer E)[]
? ReadonlyArray<DeepReadonlyObject<E>>
: T extends object
? DeepReadonlyObject<T>
: T;
type DeepReadonlyObject<T> = {
readonly [k in keyof T]: DeepReadonly<T[k]>;
}
type DeepReadonlyRootState = DeepReadonly<RootState>;
let state2: DeepReadonlyRootState = {} as DeepReadonlyRootState;
const book2 = state2.book.books[0];
book2.title = 'ssssss'; // error 더 이상 set할 수 없음
아주 읽기 무서운 코드가 나왔다. 차근차근 살펴보자.
먼저 DeepReadonly<T> 타입은 제너릭으로 들어온 T가 배열 타입이라면 배열의 원소 타입을 E로 추론하고
ReadonlyArray로 감싸고 그 안에 들어 있는 배열의 원소를 DeepReadonlyObject로 만들어 준다.
제너릭 타입이 객체라면 역시 DeepReadonlyObject로 감싸서 만들어 준다.
type DeepReadonlyObject<T> = {
readonly [k in keyof T]: DeepReadonly<T[k]>;
}
제너릭타입 T의 키 프로퍼티를 순회하면서 T[k]의 속성을 DeepReadonly로 감싸주면 재귀적으로 타고 타고 들어가
읽기 전용으로 바꾸어 준다. 실전으로 많은 코드를 작성해 봐야 겠다...
다음 시간에 계속..
'TypeScript' 카테고리의 다른 글
type challange <Easy> (0) | 2022.11.03 |
---|---|
우아한 타입스크립트 실전 코드 작성하기(1) (0) | 2022.05.26 |
여러가지 형태의 타입가드 (0) | 2022.05.09 |
Type vs Interface (0) | 2021.12.05 |
abstract 와 interface 차이점 (0) | 2021.11.08 |
댓글