.
본문 바로가기
TypeScript

type challange <Easy>

by 와칸다개발자 2022. 11. 3.

취준생을 벗어나 어엿한? 직장인이 되어서 양질의 글쓰기를 하려 했으나 쉽지않다.

 

그냥 되는대로 공부한거 기록하고 포스팅 하겠다.

 

Pick

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

타스에서 기본적으로 제공해주는 유틸리티 타입인 Pick이다.

제너릭 타입에서 뽑고싶은 속성만 뽑는다.

 

type MyPick<T, K extends keyof T> = {
  [key in K]: T[key];
}

K extends keyof T 는 K가 T 제너릭 타입이 가지고 있는 속성들의 서브타입임을 명시해준다. 

 

 

Readonly

interface Todo {
  title: string
  description: string
}

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar"
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

모든속성을 읽기전용으로 만들어야 한다.

 

type MyReadonly<T> = {
  readonly [key in keyof T]: T[key];
}

 

FirstofArray 

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
type head3 = First<[]> // expected to be never

제너릭[0] 을 반환하면 될 것 같지만 빈 배열일 경우에도 생각해야 한다.

 

type First<T extends any[]> = T extends [] ? never : T[0];

삼항연산자를 사용하여 제너릭 T가 빈배열의 서브타입일 경우 never를 반환 아님 0번째 인덱스를 반환

 

Length of Tuple

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla>  // expected 4
type spaceXLength = Length<spaceX> // expected 5

간단하게 길이를 반환해주면 된다.

type Length<T extends any[]> = T["length"];

하지만 이런 경우에는 어떨까?

const tesla = ['tesla', 'model 3', 'model X', 'model Y']as const;
type TeslaLen = Length<typeof tesla>; <---- ????

테슬라 변수는 as const를 붙임으로서 string[] 이 아닌위 tesla, model3, model X, model Y 값을 가지는 템플릿 리터럴 타입이 되었다. readonly 를 붙혀서 이렇게 작성해줘야 한다.

 

type Length<T extends readonly any[]> = T["length"];

 

Exclude 

type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

타스에서 제공해주는 기본 유틸리티 타입 두번째 제너릭 속성을 제외하고 뽑아내는 타입.

 

type MyExclude<T, U> = T extends U ? never : T;

T가 U의 서브타입이라면 never를 반환 

'a' | 'b' | 'c' 중 'a'는 두번째 제너릭으로 들어온 타입 'a'의 서브타입이므로 never를 반환하고 아님 T를 반환한다.

 

 

Awaited 

 
type X = Promise<string>
type Y = Promise<{ field: number }>

프로미스의 타입을 뽑아내는 문제이다.

type MyAwaited<T> = T extends Promise<infer R> ? R : never;

infer 키워드를 사용하여 타입을 추출할 수 있다.

 

하지만 다음과 같이 프로미스가 프로미스를 반환한다면?

프로미스가 프로미스를 반환하고 그건 또 프로미스를 반환하고 그렇다면??

재귀 타입을 써주면 된다.

type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>

type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer R> 
  ? R extends Promise<unknown>		// 1
    ? MyAwaited<R>			// 2		
    : R					// 3
  : never;				// 4

꽤 복잡하지만 천천히 뜯어보면 별거 없다. 일단 T 제너릭은 프로미스의 값만을 받는다.

 

T가 프로미스타입의 서브타입이라면 타입 R을 추출한다. 

 

1. R또한 프로미스 타입이라면

2. 재귀적으로 타고들어가 R을 던져준다.

3. 아니면 R을 반환한다.

4. T가 프로미스 타입의 서브타입이 아니라면 never를 반환한다.

 

IF

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

type If<C extends boolean, T, F> = C extends true ? T : F;

이건 좀 쉬웠다. ㅋㅋ

 

 

Concat

  type Result1 = Concat<[], []>
  type Result2 = Concat<[], [1]>
  type Result3 = Concat<[1, 2], [3, 4]>
  type Result4 = Concat<['1', 2, '3'], [false, boolean, '4']>

배열을 합쳐주면 된다.

 

type MyConcat<T extends any[], U extends any[]> = [...T, ...U];

스프레드 연산자로 합쳐주자;

 

 

Equal

이건 문제에 없지만 후에 나올 Include 타입 문제에 쓰이기 때문에 한번 알아보자

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

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

interface OtherType {
  name:string;
  money: number;
}

type Result1 = Equals<Person, Person>;  // expected true
type Result2 = Equals<Person, Person2>;  // expected true
type Result3 = Equals<Person2, OtherType>;  // expected false

말그대로 두 타입이 동일한가에 대한 타입이다.

 

type Equals<X, Y> = 
  (<T>() => T extends X ? "A" : "B") extends (<T>() => T extends Y ? "A" : "B")
  ? true
  : false;

이거 솔직히 해석이 잘 안된다.... 어떤 천재가 이런 괴팍하고 아름다운 타입을 개발했을까...

오직 연습뿐...

 

Include

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

타입이 포함되어있는지를 묻는 문제이다.

 

Array.include 로 순회하면서 판별하면 좋겠지만 이건 타입이니까... 

 

type Equal<X, Y> =
  (<T>() => T extends X ? "A" : "B") extends (<T>() => T extends Y ? "A" : "B")
  ? true
  : false;

type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] // 0
  ? Equal<First, U> extends true	// 1
    ? true				// 2
    : Includes<Rest, U>			// 3
  : false;				// 4

천천히 살펴보자. 

 

0. 제너릭 T는 첫번째 원소와 나머지 연산자로 타입을 추출한다.

1. Equal 타입으로 첫번째 원소와 U를 비교를 하고 true의 서브타입인지 판별한다.

2. true의 서브타입이면 true를 반환한다.

3. 1에서 판별한 타입이 false의 서브타입이라면 즉 포함되지 않았다면 재귀 타입으로 

추출한 Rest 타입을 U와 함께 재귀적으로 수행해준다.

4. 아니면 false를 반환한다. 

 

 

 

Push

type Result = Push<[1, 2], '3'> // [1, 2, '3']

type Push<T extends any[], U> = [...T, U];

 

이건 쉽다!

 

 

Unshift

type Result = Unshift<[1, 2], 0> // [0, 1, 2,]

type Unshift<T extends any[], U> = [U, ...T];

 

 

Parameters 

const foo = (arg1: string, arg2: number): void => {}

type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]

함수의 파라미터를 뽑는다.

 

type MyParameters<T extends (...args: any[]) => any> = 
  T extends (...args: infer R) => any
  ? R
  : never;

파라미터를 infer로 추출하고 R로 반환 아님 never

 

EASY다운 문제도 있었지만

아닌 ? 것도 있다.

댓글