(11)

[Type Challenge] LastIndexOf + 이럴 때 Equal Type을 쓰는거구나?

사전지식해당 글을 읽기 전에 Type Challenge에서 제공하는 Equal Type에 대한 이해도가 필요합니다.아래 포스팅은 제가 열심히 작성해 본 글이니 확인 한번 하시면 좋을 것 같아요! https://dev-iamkanguk.tistory.com/entry/TypeScript-Type-Challenge-Equal-Type-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0 [TypeScript + Type-Challenge] Equal Type 분석하기Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉dev..

[Type Challenge - MEDIUM] Chainable Options

https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-options/README.md type-challenges/questions/00012-medium-chainable-options/README.md at main · type-challenges/type-challengesCollection of TypeScript type challenges with online judge - type-challenges/type-challengesgithub.com 타입스크립트 스터디도 진행하면서 타입에 조금 더 자신이 생겼다고 생각했지만 이 문제를 보자마자 이해 자체를 하지 못했다.그래서 다른 분..

[Type Challenge] Omit 문제에 대한 고찰 (readonly 관련)

https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.md type-challenges/questions/00003-medium-omit/README.md at main · type-challenges/type-challengesCollection of TypeScript type challenges with online judge - type-challenges/type-challengesgithub.com TypeScript에서 지원하는 Utility Type인 Omit을 직접 구현해보는 문제였다.해당 문제를 구현하면서 헷갈렸던 부분을 정리하려고 한다. Omit에 대한 자세한 설명을 ..

[타입챌린지-MEDIUM] 8: Readonly 2

문제 https://github.com/type-challenges/type-challenges/blob/main/questions/00008-medium-readonly-2/README.md type-challenges/questions/00008-medium-readonly-2/README.md at main · type-challenges/type-challenges Collection of TypeScript type challenges with online judge - type-challenges/type-challenges github.com 정답 // 1번째 방법과 2번째 방법의 차이는 내장 메서드인 Omit을 사용했냐 안했냐의 차이! // 다양하게 구현할 수 있었던 문제였던 것 같음. 하..

[Type Challenge] LastIndexOf + 이럴 때 Equal Type을 쓰는거구나?

Type Challenge 2024. 11. 5. 11:45

사전지식

해당 글을 읽기 전에 Type Challenge에서 제공하는 Equal Type에 대한 이해도가 필요합니다.

아래 포스팅은 제가 열심히 작성해 본 글이니 확인 한번 하시면 좋을 것 같아요!

 

https://dev-iamkanguk.tistory.com/entry/TypeScript-Type-Challenge-Equal-Type-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0

 

[TypeScript + Type-Challenge] Equal Type 분석하기

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉

dev-iamkanguk.tistory.com

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/05317-medium-lastindexof/README.md

 

type-challenges/questions/05317-medium-lastindexof/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

풀이

import { Equal, Expect } from "../../utils"

namespace iamkanguk_lastIndexOf {
    export type LastIndexOf<T extends unknown[], U> = T extends [...infer F, infer L] ? L extends U ? F['length'] : LastIndexOf<F, U> : -1;
}

namespace TestCase {
    // OK
    type TC1 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>;
    // OK
    type TC2 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>;
    // OK
    type TC3 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[0, 0, 0], 2>, -1>>;
    // NOPE
    type TC4 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
    // NOPE
    type TC5 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;
}

 

- 문제는 그렇게 어렵지 않았다. 하나의 배열을 둘로 쪼개는데 앞에서부터 접근하는게 아니라 뒤에서부터 접근한다고 생각하면 쉬웠던 것 같다. 충분히 이해할 수 있을 것이라고 생각한다.

- 하지만 문제는 위의 코드를 보면 TC4와 TC5가 통과를 안한다는 것이다. 로직적으로 잘못된 부분이 없는데 왜 통과를 안하는 것일까?

- 결론은 L extends U 부분에서 문제가 발생할 수 있다는 것이다.

어느 부분에서 문제가 발생할 수 있는건지?

TC1부터 TC3은 literal 타입이기 때문에 extends를 해도 정확한 비교가 되기 때문에 괜찮다.

하지만 TC4와 TC5를 보면 U가 Primitive 타입(원시 타입)이다. 이는 extends로는 정확한 비교가 안되기 때문에 에러가 발생하고 있는 것이다.

 

지금부터는 위에서 구현한 로직대로 차근차근 설명해보도록 하겠다.

보시기 편하게 LastIndexOf 타입을 다시 보여드리겠습니다.

type LastIndexOf<T extends unknown[], U> =
	T extends [...infer F, infer L]
    	? L extends U
        	? F['length']
            : LastIndexOf<F, U>
        : -1;

 

예시1 - TC4)

결과값이 4가 출력되어야 한다. 제일 뒤에 있는 number 타입을 찾아야 하기 때문이다.

편의상 string은 s, number는 n으로 표현하겠다.

type TC4 = Expect<Equal<LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
  F L L extends U(number) Finish? LastIndex Equal with U?
1 [s, 2, n, 'a', n] 1 1 extends number -> true YES 5 NO
2 - - - -    

 

표를 보면 이해가 되실지 모르겠지만 결론은 1 extends number가 true로 추론되어서 1의 index인 5를 가져온거고 4와 5는 다르기 때문에 틀렸다고 표시되는 것이다.

 

여기서 살짝 의문이 들 수 있다. 그러면 L extends U가 아니라 U extends L을 하면 통과되는거 아냐?

맞다. number extends 1은 false이기 때문에 다음 스텝으로 넘어갈 것이고 정상적으로 4를 가져올 것이다.

 

하지만 U extends L로 바꿔도 다음 예시에서 에러가 발생한다.

예시2 - TC5)

type TC5 = Expect<Equal<LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;

 

  F L L extends U(any) Finish? LastIndex Equal with U?
1 [s, any, 1, number, 'a', any] 1 1 extends any -> true YES 6 NO
2 - - - - - -

 

위랑 비슷한 상황이다. 1 extends any가 true로 추론되어서 1의 index인 6을 가져온 것이고 6과 5는 다르기 때문에 틀렸다고 표시된다.

U extends L로 바꾸면 어떻게 될까? 그러면 1 extends any가 아니라 any extends 1이 될 것이다. 이는 결과가 boolean이다.

 

여기서 나오는 결론은 뭐냐면 이런 상황이 발생될 때는 extends 보다 더 정확한 비교가 필요하다는 것이다.

그래서 우리는 소스코드를 다음과 같이 수정할 수 있다.

최종 소스코드

import { Expect } from "../../utils"

export type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

namespace iamkanguk_lastIndexOf {
    export type LastIndexOf<T extends unknown[], U>
    	= T extends [...infer F, infer L]
        	? Equal<U, L> extends true   // 여기!
            	? F['length']
                : LastIndexOf<F, U>
            : -1;
}

namespace TestCase {
    // OK
    type TC1 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[1, 2, 3, 2, 1], 2>, 3>>;
    // OK
    type TC2 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 7>>;
    // OK
    type TC3 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[0, 0, 0], 2>, -1>>;
    // OK
    type TC4 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, 2, number, 'a', number, 1], number>, 4>>;
    // OK
    type TC5 = Expect<Equal<iamkanguk_lastIndexOf.LastIndexOf<[string, any, 1, number, 'a', any, 1], any>, 5>>;
}

 

L extends U 부분을 Type Challenge에서 제공하는 Equal Type을 적용하면

더욱 정확한 비교를 통해 정답처리를 받을 수 있다.

 

이전 포스팅에서도 좋은 참고자료를 첨부했지만

아래 링크를 확인해보면 Equal Type에 대한 다른 설명도 있으니 참고하면 좋을 것 같다.

https://github.com/type-challenges/type-challenges/discussions/9100#discussioncomment-6896958

 

Could you explain how does `Equal` type implementation work? · type-challenges type-challenges · Discussion #9100

type-challenges/utils/index.d.ts Line 7 in e3cc0d3 export type Equal<X, Y> =

github.com

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[Type Challenge - MEDIUM] Chainable Options

Type Challenge 2024. 10. 2. 15:44

https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-options/README.md

 

type-challenges/questions/00012-medium-chainable-options/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

타입스크립트 스터디도 진행하면서 타입에 조금 더 자신이 생겼다고 생각했지만 이 문제를 보자마자 이해 자체를 하지 못했다.

그래서 다른 분들이 어떤 생각을 가지고 풀이를 했는지를 먼저 파악하고, 직접 생각하면서 풀려고 노력을 해봤다.

시작을 어떻게 해야할지만 파악한다면 크게 어렵지는 않은 문제였던 것 같은데, 시작을 어떻게 해야할지 파악하는게 가장 어려웠던 것 같다.

 

해당 글은 다른 분들의 풀이를 참고했습니다!

https://suloth.tistory.com/52

 

타입챌린지 : 12-Chainable Options (medium)

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-o

suloth.tistory.com

 

1단계: option 메서드가 Chainable을 반환한다는 점을 파악해야 한다.

type Chainable = {
    option(key: string, value: any): Chainable;
    get(): any;
}

declare const a: Chainable;

const result1 = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .option('name', 'type-challenges')
  .get()

 

위의 소스코드를 보면 Chainable이라는 타입을 가지는 a라는 변수에 option 메서드를 체이닝 하면서 적용하고 있다.

즉, option이라는 메서드가 Chainable을 반환을 해야 동작이 가능한 소스코드라는 점이다.

 

a.option('foo', 123) 이라는 코드가 Chainable 타입의 데이터를 반환해야, 반환된 Chainable 타입의 데이터에서 option 메서드를 추가로 사용할 수 있기 때문이다.

 

2단계: 상태 유지를 위해서는 제네릭을 사용해야 한다.

option 메서드가 Chainable을 반환하면서 체이닝이 되어야 한다. 그러기 위해서는 option 메서드가 반환하는 값이 객체에 대한 정보를 가지고 있어야 할 것이다. 

 

1단계에서 보여지는 result1을 보면 foo라는 키에는 123의 타입인 number가 할당되어야 하고 두 번째 option 메서드가 사용되는 시점에는 1단계에서 사용된 option의 리턴값을 기억하고 있어야 한다. 이런 상황을 구현하기 위해서는 타입 레벨에서는 제네릭을 사용해야 한다.

type Chainable<T = {}> = {
    option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>;
    get(): any;
}

 

 

- Chainable<T = {}>제네릭을 통해 처음 기본 객체(빈 객체)를 선언해주어야 체이닝이 가능하다.

- option<K extends string, V> : K는 string 타입이고, V도 선언해준다.

- (key: K extends keyof T ? never : K, value: V) : key는 K가 T의 key 값이면 중복 방지를 위해 never를 반환하고, 아니면 K를 그대로 적용한다. value는 제네릭 V를 그대로 적용해준다.

- Chainable<T & Record<K, V>> : 현재 객체의 정보를 저장하고 넘기기 위해서 제네릭을 설정해주는 것이다. T에다가 매개변수로 받은 K와 V 조합을 T에다가 적용을 시켜줘서 반환한다는 의미이다.

 

3단계: get 메서드의 리턴 타입은 무엇일까?

필자는 처음 구현할 때 ReturnType 뭐시기 해서 사용했었는데 그럴 필요가 없었다. 그냥 T 그대로 리턴하면 되는 것이다.

왜냐하면 결국에 get은 option 체이닝을 진행하고 마지막에 get 메서드를 사용하기 때문이다.

type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>
  get(): T
}

 

4단계: 마지막 오류 해결

위의 코드까지 적용해보면 마지막 테스트 케이스에만 오류가 발생하는데 이유를 확인해보니까..

const result3 = a
  .option('name', 'another name')
  // @ts-expect-error
  .option('name', 123)
  .get()

type Expected3 = {
  name: number
}

 

위와 같이 name 이라는 같은 키에서 처음에는 string을, 그다음에는 123이라는 number 타입을 적용했다.

Expected3 에는 name이라는 키에는 number 타입이 적용되었다. 즉, 마지막으로 입력된 값이 남는다는 것이다.

type Chainable<T = {}> = {
  option<K extends string, V>(key: K extends keyof T ? never : K, value: V): Chainable<Omit<T, K> & Record<K, V>>
  get(): T
}

 

그래서 option 메서드의 반환 타입을 수정했는데 T에서 K 값을 가지고 있으면 기존 K 값을 제거해주고 새로운 K 값을 넣어주면 된다.

해당 로직을 적용하기 위해서는 Omit을 사용하면 될 것 같다.

 

 

 

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[Type Challenge] Omit 문제에 대한 고찰 (readonly 관련)

Type Challenge 2024. 9. 27. 17:19

https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.md

 

type-challenges/questions/00003-medium-omit/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

TypeScript에서 지원하는 Utility Type인 Omit을 직접 구현해보는 문제였다.

해당 문제를 구현하면서 헷갈렸던 부분을 정리하려고 한다. Omit에 대한 자세한 설명을 하지는 않겠다.

(Omit<T, K> -> T에서 K들을 빼고 리턴하는 것>

풀이1)

type cases = [
  Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]

interface Todo1 {
  readonly title: string
  description: string
  completed: boolean
}

interface Expected3 {
  readonly title: string
}

// 메인 풀이
type FirstMyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

 

- T의 Key들을 순회할 때 P라는 변수를 가지고 순회를 할건데 P가 K에 할당이 가능하다면 never를 통해 Index Signature에서 제외시키고, 할당이 불가능하다면 적용한다. 그리고 해당 경우에는 T를 P로 접근하여 value를 가져온다.

- 가장 직관적이고 무난한 풀이라고 생각이 된다.

 

풀이2) Exclude를 사용한 풀이 (오답)

type cases = [
  Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]

interface Todo1 {
  readonly title: string
  description: string
  completed: boolean
}

interface Expected3 {
  readonly title: string
}

// 메인 풀이
type SecondMyOmit<T, K extends keyof T> = {
  [P in Exclude<keyof T, K>]: T[P];
};

 

- Omit은 T에서 K를 제외하고 반환하는 타입이기 때문에 Exclude를 사용할 수 있다는 의미이다.

- Exclude를 사용해서 T에서 K를 제외한 타입을 가져오고, 그것을 P라는 변수를 통해 순회해서 객체를 만들어준다는 의미이다.

- 하지만 해당 코드는 TypeScript Playground에서 에러를 발생시키고 있다. 이유는 무엇일까? 이후에 확인해보자.

에러가 발생하는 이유?

확인해보니 readonly를 인식하지 못해서 에러가 발생하는 것 같다.

그래서 왜 케이스를 통과를 못하는지 생각을 해보다가, keyof를 적용할 때 readonly 부분을 잡아주지 못해서 그랬다고 생각했다.

하지만 기본적으로 객체 안에서 keyof를 사용하면 readonly는 무시되지 않는 다는 것이다. 이게 무슨말인가?

interface Todo {
   readonly title: string;
   description: string;
}

type keyOfTodo = keyof Todo;   // "title" | "description"

type Hello = {
	[P in keyof Todo]: T[P]
};   // { readonly title: string; description: string; }

 

- Todo라는 타입에 keyof Todo를 하면 readonly가 제거된 상태로 결과가 출력된다.

- 하지만, Hello라는 타입을 보면 객체 안에서 keyof Todo를 하면 readonly가 유지된 채로 결과가 출력된다.

- 이 부분을 인지하지 못해서 헷갈렸던 것 같다.

 

그러면 다시 위의 코드가 에러가 발생하는 이유에 대해서 생각해보자.

Exclude는 결과 타입이 객체가 아닌 유니온 타입이다.

type Fruit = "cherry" | "banana" | "strawberry" | "lemon";

type RedFruit = Exclude<Fruit, "banana" | "lemon">;
// type RedFruit = "cherry" | "strawberry" 와 같다.

 

방금 위에서 객체 타입에서 사용한 keyof는 readonly가 유지된다고 언급했는데, Exclude의 결과 타입이 유니온 타입이기 때문에 readonly가 유지가 되지 않는다. 그래서 P in Exclude<keyof T, K> 를 하게 되면 readonly가 빠진 채로 순회가 되기 때문에 에러가 발생하고 있는 것이다.

 

풀이3) Exclude를 활용하면서 해결하는 방법

 

일단 왜 에러가 발생하는지는 파악이 되었다. 그러면 Exclude를 통해서 해결하는 방법이 있을까?

type cases = [
  Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]

interface Todo1 {
  readonly title: string
  description: string
  completed: boolean
}

interface Expected3 {
  readonly title: string
}

// 메인 풀이
type ThirdMyOmit<T, K extends keyof T> = {
  [P in keyof T as Exclude<P, K>]: T[P];
};

 

아까 봤던 코드랑 약간 다르다. 일단 as 라는 키워드를 "그런데" 라고 해석해보자.

인지했으면 코드를 천천히 해석해보자.

 

먼저, Exclude<P, K>는 P extends K ? never : T; 라고 치환할 수 있을 것 같다.

치환한 뒤의 코드를 살펴보자.

type cases = [
  Expect<Equal<Expected3, ThirdMyOmit<Todo1, 'description' | 'completed'>>>,
]

interface Todo1 {
  readonly title: string
  description: string
  completed: boolean
}

interface Expected3 {
  readonly title: string
}

// 메인 풀이
type ThirdMyOmit<T, K extends keyof T> = {
  [P in keyof T as P extends K ? never : T]: T[P];
};

 

이렇게 보면 뭔가 조금씩 보인다.

 

1. keyof를 객체 내에서 사용했기 때문에 readonly 상태가 유지된다.

2. 유지되는 상태에서 P를 추출했기 때문에 P에는 readonly가 적용된 Property Key가 올 수 있다.

3. 그 P가 K에 할당될 수 있으면 빼고, 할당이 되지 않는다면 Index Signature 상태로 유지한다.

 

그래서 우리는 Exclude 유틸리티 타입을 사용해서 문제를 해결할 수 있었다.


 

최대한 다른 분들이 보기에 이해하기 쉽게 작성해봤는데 이해가 되실지는 모르겠다.

그래도 필자와 같이 해당 문제를 풀 때 이런 고민을 한적이 있다면 도움이 되었으면 좋겠다.

 

핵심은 객체 내에서의 keyof는 readonly를 유지할 수 있다는 것.

 

NestJS 오픈채팅 톡방에서 도움을 주신 Michael님 감사합니다.

 

- https://chanhuiseok.github.io/posts/ts-3/

- https://bkdragon0228.tistory.com/m/3

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지-MEDIUM] 5153: IndexOf

Type Challenge 2024. 4. 9. 18:57

문제

https://github.com/iamkanguk97/type-challenges/blob/main/questions/05153-medium-indexof/README.md

 

type-challenges/questions/05153-medium-indexof/README.md at main · iamkanguk97/type-challenges

Collection of TypeScript type challenges with online judge - iamkanguk97/type-challenges

github.com

 

풀이

/* _____________ Your Code Here _____________ */
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type IndexOf<T, U, L extends unknown[] = []>
  = T extends [infer A, ...infer B] 
    ? Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>
    : -1

type cases = [
  Expect<Equal<IndexOf<[1, 2, 3], 2>, 1>>,
  Expect<Equal<IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 2>>,
  Expect<Equal<IndexOf<[0, 0, 0], 2>, -1>>,
  Expect<Equal<IndexOf<[string, 1, number, 'a'], number>, 2>>,
  Expect<Equal<IndexOf<[string, 1, number, 'a', any], any>, 4>>,
  Expect<Equal<IndexOf<[string, 'a'], 'a'>, 1>>,
  Expect<Equal<IndexOf<[any, 1], 1>, 1>>,
]

 

풀이 해설

아예 생각지도 못했다. 진짜 내가 많이 부족하다고 느꼈다.

필자는 배열을 순회해야겠다 싶었는데 전혀 다른 풀이법이었다.

당분간은 지금까지 풀었던 타입 챌린지를 복습하면서 타입스크립트 공식문서도 틈틈이 읽어봐야 할 것 같다.

 

다른 분들의 풀이를 보면 이해가 정말 쉽게 된다. 항상 느끼는데 왜 문제를 보면 이런 풀이법이 떠오르지 않을까 싶다 ㅠ 많은 노력을 해야겠다.

  • type IndexOf<T, U, L extends unknown[] = []>: 문제에서는 T와 U만 있었을 것이다. 하지만 우리는 제네릭 L을 추가할 것인데, 타입은 배열 타입이고 Default는 빈 배열이다.
  • T extends [infer A, ...infer B] ? <> : -1: 일단 크게 한번 보자. T 배열을 A 원소 1개와, 나머지 원소들을 합친 B 배열로 나눌 수 있다면 <>를 실행하고, 그렇지 않으면 -1을 반환한다는 것이다. -1을 반환한다는 것은 원소를 못찾았을 경우를 의미한다. 해당 설명만 보면 으이? 할 수 있는데 아래 설명도 같이 보면 이해가 될 것이라고 생각한다.
  • (위의 괄호) Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>: 제일 핵심 부분이다!!
    • Equal<A, U>: 문제에서 제공하는 util 라이브러리를 사용하였다. 사용하지 않을 것이라면 직접 Equal 타입을 구현해도 무방하다. 여기서 A는 위에서 배열을 두개로 나눌 때 첫 원소를 의미한다. 그리고 U는 문제에서 찾으려고 하는 값이다.
    • extends true ? L['length'] : IndexOf<B, U, [...L, A]>
      • Equal의 결과가 true이면 L의 길이를 반환하고, 그렇지 않으면 IndexOf를 재귀적으로 실행한다는 것이다.
      • 이 때, IndexOf에 B, U, [...L, A]를 넣는데 한번 살펴보자.
        • B는 위에서 나눈 배열을 의미한다.
        • U는 문제에서 찾으려고 하는 값 그대로 넣으면 된다.
        • [...L, A] 는 비교한 값들을 누적한다는 의미이다.

 

일단 코드적으로만 해석해서 설명해봤는데 아마 잘 이해가 될 지는 모르겠다. 그런데 머리속으로 생각하면 그렇게 어렵지 않다.

딱 한마디로만 정의하면 "L에다가 기전에 비교한 값들 A를 누적하는 것이다. 현재값인 A가 U랑 일치하면 L 배열의 길이가 A의 인덱스이기 때문에 그대로 반환하면 된다."

 

예시를 하나 들어서 설명해보겠다. 테스트 케이스는 2번째 케이스를 사용하겠다.

 

<First Step>

- T = [2, 6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // L = []

- T extends [infer A, ...infer B] >>> A: 2, B: [6, 3, 8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(2)와 U(3)은 같지 않기 때문에 False. 따라서 조건에 의해 IndexOf 부분을 실행한다.

   -- IndexOf<B, U, [...L, A]>: B = [6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // [...L, A] = [2]

 

<Second Step>

- T = [6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // L = [2]

- T extends [infer A, ...infer B] >>> A: 6, B: [3, 8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(6)와 U(3)은 같지 않기 때문에 False. 따라서 조건에 의해 IndexOf 부분을 실행한다.

   -- IndexOf<B, U, [...L, A]>: B = [3, 8, 4, 1, 7, 3, 9] // U = 3 // [...L, A] = [2, 6]

 

<Third Step>

- T = [3, 8, 4, 1, 7, 3, 9] // U = 3 // L = [2, 6]

- T extends [infer A, ...infer B] >>> A: 3, B: [8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(3)와 U(3)은 서로 같기 때문에 L['length']를 반환한다!

   -- 따라서 현재 L['length']는 2이기 때문에 2가 반환되고, 실제로 3은 index가 2인 것을 확인할 수 있다.


<2023-04-10 추가>

5317번 LastIndexOf 라는 문제가 있는데 비슷하게 풀어볼 수 있으니까 한번 풀어보길 바란다!

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지-MEDIUM] 9: Deep Readonly

Type Challenge 2024. 4. 4. 22:11

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/00009-medium-deep-readonly/README.md

 

type-challenges/questions/00009-medium-deep-readonly/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

풀이

/* _____________ 여기에 코드 입력 _____________ */

type DeepReadonly<T> = {
  readonly [P in keyof T]: keyof T[P] extends never ? T[P] : DeepReadonly<T[P]>
}


/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<DeepReadonly<X1>, Expected1>>,
  Expect<Equal<DeepReadonly<X2>, Expected2>>,
]

type X1 = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
}

type X2 = { a: string } | { b: number }

type Expected1 = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
      readonly l: readonly [
        'hi',
        {
          readonly m: readonly ['hey']
        },
      ]
    }
  }
}

type Expected2 = { readonly a: string } | { readonly b: number }

풀이 해설

이 문제의 핵심은 재귀적으로 구현을 해야한다는 점이다. depth 별로 확인하면서 readonly를 모두 추가해주어야 한다.

  • readonly [P in keyof T]: 앞에 readonly를 붙여줄건데 T의 key list를 순회한다. 순회할 때 P로 변수지정을 해준다.
  • keyof T[P] extends never ? T[P] : DeepReadonly<T[P]>
    • keyof T[P]: 만약에 특정 key에 대해 value가 또 객체의 구조일 경우를 생각해서 keyof를 붙여주었다.
    • extends never ? T[P] : DeepReadonly<T[P]>: T[P]의 key들을 봤는데 key가 없는 경우(특정 key에 대한 value가 객체인 구조가 아닌경우)는 T[P] 그대로를, 그렇지 않은 경우에는 해당 value(객체)를 가지고 다시 DeepReadonly를 적용해준다.

 

재귀를 어떻게 사용해야하지? 라고 생각을 할 수 있으면 충분히 풀 수 있는 문제였던 것 같다.

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지-MEDIUM] 8: Readonly 2

Type Challenge 2024. 3. 27. 17:12

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/00008-medium-readonly-2/README.md

 

type-challenges/questions/00008-medium-readonly-2/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

정답

// 1번째 방법과 2번째 방법의 차이는 내장 메서드인 Omit을 사용했냐 안했냐의 차이!
// 다양하게 구현할 수 있었던 문제였던 것 같음. 하지만 어려웠음
// Intersection을 이용해야 겠다는 생각을 못함.

/* ------------- 1번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in keyof T as P extends K ? P : never]: T[P]
} & {
  [P in keyof T as P extends K ? never : P]: T[P]
}

/* ------------- 2번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & {
  [P in keyof Omit<T, K>]: T[P]
}

// test case
type cases = [
  Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
  Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
  Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>,
]

// @ts-expect-error
type error = MyReadonly2<Todo1, 'title' | 'invalid'>

interface Todo1 {
  title: string
  description?: string
  completed: boolean
}

interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}

interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}

 

풀이해설 및 후기

일단, 대충 어떤 식으로 구현을 해야겠다라고 생각은 했는데 Intersection을 사용해야 겠다는 생각을 전혀 못했다. 이 타입 챌린지를 풀어보면서 타입스크립트에 대해 자세히 공부해보려고 책을 구매해야 겠다는 생각이 많이 들었다.

 

먼저 방법들을 설명하기 전에 공통적으로 알아야 할 부분을 먼저 언급하자면, MyReadonly2<T, K>에서 K가 null일 수도 있다. K가 null이면 T의 모든 Property들을 readonly 처리를 해야한다. 그래서 필자가 작성한 정답을 보면 K extends keyof T = keyof T라고 되어있다. 이 부분을 포함시켜야 한다. 해당 표현은 Default 값을 설정해줄 수 있는 부분인데 K가 Null일 경우에는 keyof T로 설정하라는 의미이다. 꼭 참고하자!

1번째 방법

/* ------------- 1번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in keyof T as P extends K ? P : never]: T[P]
} & {
  [P in keyof T as P extends K ? never : P]: T[P]
}

 

직관적인 풀이법이다. 우리는 "K에서 설정한 Property들이면 readonly 속성을 추가해주고, 그렇지 않으면 그대로 넣자" 만 지키면 된다. 중요한 점은 Intersection을 사용해야 한다는 점....! 그리고 이전 포스팅에서 배웠던 as(그런데)를 사용하면 쉽게 해결할 수 있었다.

  • readonly [P in keyof T as P extends K ? P : never]: T[P]: "readonly 속성을 앞에 추가해줄거야. P가 T의 key여야 해. 그런데 P가 K에 속해있으면 추가해주고, 그렇지 않으면 추가 안할게" 라는 뜻이다. 
    • Test-case 2번을 예시로 들어보면 { readonly title: string, readonly description?: string }
  • 그리고 우리는 &을 통해 반대 케이스도 엮어준다. (Intersection 설명 링크 = 여기!)
  • [P in keyof T as P extends K ? never : P]: T[P]: 위에와 반대로 P가 K에 속해있으면 never를, 속해있지 않으면 반환한다.
    • Test-case 2번을 예시로 들어보면 { completed: boolean }
  • 위의 2개의 결과를 엮으면 { readonly title: string, readonly description?: string, completed: boolean } 이 반환된다.

2번째 방법

/* ------------- 2번째 방법 ------------- */
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & {
  [P in keyof Omit<T, K>]: T[P]
}

 

사실 1번째 방법이랑 동일한 방법이다. 하지만 TypeScript에서 제공하는 Omit을 사용했다는 것에 대해 차이가 있다. Omit<T, K>는 T에서 K를 제외한 나머지를 통해 타입을 정의하기 때문에 K에 속하는 것들만 readonly를 추가해주고, Omit을 통해 K에 속해있지 않은 것들만 readonly를 추가해주지 않는 식으로 구현하면 된다.


- https://kyounghwan01.github.io/blog/TS/fundamentals/utility-types/#partial

 

typescript - 유틸리티 타입 (Partial, Omit, Pick)

typescript - 유틸리티 타입 (Partial, Omit, Pick), 타입스크립트, ts

kyounghwan01.github.io

 

- https://kscodebase.tistory.com/632?category=1330674

 

Readonly type 구현

GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge Collection of TypeScript type challenges with online judge - GitHub - type-challenges/type-challenges: Collection of TypeScript type challenges with online

kscodebase.tistory.com

 

- https://joshua1988.github.io/ts/guide/operator.html#union-type%EC%9D%98-%EC%9E%A5%EC%A0%90

 

연산자를 이용한 타입 정의 | 타입스크립트 핸드북

Union Type 유니온 타입(Union Type)이란 자바스크립트의 OR 연산자(||)와 같이 A이거나 B이다 라는 의미의 타입입니다. 아래 코드를 보겠습니다. 위 함수의 파라미터 text에는 문자열 타입이나 숫자 타입

joshua1988.github.io

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지-MEDIUM] 3: Omit

Type Challenge 2024. 3. 24. 23:51

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.md

 

type-challenges/questions/00003-medium-omit/README.md at main · type-challenges/type-challenges

Collection of TypeScript type challenges with online judge - type-challenges/type-challenges

github.com

 

풀이

type MyOmit<T, K extends keyof T> = {
  [U in keyof T as U extends K ? never : U]: T[U]
}

/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Expected1, MyOmit<Todo, 'description'>>>,
  Expect<Equal<Expected2, MyOmit<Todo, 'description' | 'completed'>>>,
]

// @ts-expect-error
type error = MyOmit<Todo, 'description' | 'invalid'>

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

interface Expected1 {
  title: string
  completed: boolean
}

interface Expected2 {
  title: string
}

 

풀이 해설

  • type MyOmit<T, K extends keyof T>: 먼저, MyOmit에서는 T와 K를 받는다. 그런데 K는 T의 key여야 한다.
  • [U in keyof T as U extends K ? never : U]: T[U]
    • U in keyof T는 T의 키값들을 U라는 타입 인자로 순회하겠다는 의미이다.
    • as U extends K ? never : U << 이 부분이 제일 중요하다. as는 그런데라고 해석하면 된다. 해석을 쭉 해보면, U는 T의 키값이다. 그런데 만약 U가 K를 상속하면 never를 반환하고 그렇지 않으면 U를 반환한다는 뜻이다.

 

후기

 

약간 큰 그림까지는 푼 것 같은데, as 하는 부분을 몰라서 못풀었던 문제이다. 한번 더 복습해서 내 것으로 만들자!

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지-EASY] 189: Awaited

Type Challenge 2024. 3. 18. 14:46

문제

https://github.com/iamkanguk97/type-challenges/blob/main/questions/00189-easy-awaited/README.md

 

풀이

type MyAwaited<T extends PromiseLike<any>> 
  = T extends PromiseLike<infer R> ? 
      R extends PromiseLike<any> ?
        MyAwaited<R>
      : R
    : never;
    
    
/* _____________ 테스트 케이스 _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
  Expect<Equal<MyAwaited<Z1>, string | boolean>>,
  Expect<Equal<MyAwaited<T>, number>>,
]

// @ts-expect-error
type error = MyAwaited<number>

 

풀이 해설

(1) MyAwaited<T extends PromiseLike<any>>

우리는 문제에서 알 수 있듯이 T는 Promise 또는 then을 메서드로 가지는 Promise와 같은 타입을 받아야 한다. 그래서 extends를 통해 제네릭의 타입을 제한해준다. 그래서 extends PromiseLike<any>라고 코드를 작성했는데 왜 Promise<any>는 되지 않는지에 대해서 아래에서 설명하도록 하겠다.

(2) T extends PromiseLike<infer R> ? {} : never

중간에 조건연산자 있는 부분을 빼고 큰 부분만 봐보자. T는 Promise 형태를 가지는데 그렇지 않으면 never를 반환한다. Promise 형태로 되어있다면 R에 해당 타입이 할당된다. 테스트 케이스와 함께 보자.

  • Promise<string> => R: string
  • Promise<{ field: number }> => R: { field: number }
  • Promise<Promise<string | number>> => R: Promise<string | number>

(3)  R extends PromiseLike<any> ? MyAwaited<R> : R

2번에서 반환받은 R이 Promise 형태를 가지고 있으면 MyAwaited를 재귀적으로 호출하는 것이고, 그렇지 않다면 그대로 R을 반환한다.

그러면 지금 위 2번의 3개 케이스를 보면 1번과 2번은 그대로 반환될 것이다. 하지만 3번 같은 경우에는 지금 Promise 형태를 띄고 있기 때문에 MyAwaited를 재귀적으로 호출하게 된다.

 

3번 케이스를 예시로 들면 string | number가 다시 R에 할당이 될 것이고, 3번에서 R이 Promise 형태를 띄지 않기 때문에 그대로 반환되어 string | number가 나오게 된다.

 

몰랐던 사실

사실 위의 설명으로 이해가 가능할 것이라고 생각한다. 하지만 필자가 제일 이해가 되지 않았던 부분은 PromiseLike를 사용해야 문제가 정답처리가 된다는 점이었다. Promise와 PromiseLike의 가장 큰 차이점을 보면 Promise는 현재 then ,catch ,finally가 구현이 되어있다. 하지만 PromiseLike는 오로지 then만 정의되어 있다.

 

마지막 테스트 케이스를 보면 Promise의 then 메서드가 작성되어 있지만 Promise 형태는 아니었다. 즉, Promise 동작과 비슷하게 then 메서드를 가진 타입이라고 할 수 있다. 그래서 우리는 이런 경우를 처리하기 위해 PromiseLike를 사용했다고 생각하면 된다.

 

후기

필자는 좀 많이 어려웠던 문제였던 것 같다. 아예 감이 잡히지 않아서 좀 고민하다가 그냥 다른 분들의 풀이를 보면서 분석해가며 이해를 하게 되었다. 


참고자료

- https://jaenny-dev.tistory.com/6

 

[타입챌린지/type-challenge] Awaited

문제 Promise와 같은 타입에 감싸진 타입이 있을 때, Promise 내부에 감싸진 타입이 무엇인지 어떻게 알 수 있을까요? 예를 들어, Promise이 있을 때 ExampleType을 어떻게 얻을 수 있을까요? type ExampleType =

jaenny-dev.tistory.com

 

- https://suloth.tistory.com/31

 

타입챌린지 : 189-Awaited (easy)

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. https://github.com/type-challenges/type-challenges/blob/main/questions/00189-easy-awaited/READM

suloth.tistory.com

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지 - EASY] 14 - First of Array

Type Challenge 2024. 3. 10. 17:36

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/00014-easy-first/README.md

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

Expect<Equal<First<[3, 2, 1]>, 3>>,
Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
Expect<Equal<First<[]>, never>>,
Expect<Equal<First<[undefined]>, undefined>>,

 

풀이

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

// S2
type First<T extends any[]> = T["length"] extends 0 ? never : T[0];

// S3
type First<T extends any[]> = T extends [infer U, ...any] ? U : never;

 

How to Solve?

먼저, 문제에서 배열타입을 다루기 때문에 제네릭 T가 배열 타입이라는 것을 명시해줘야 해서 extends any[]를 설정해주어야 한다.

우리는 첫 번째 원소에 접근을 해야하니까 T[0]으로만 하면 되는 줄 알았더니, 빈 배열에 대한 예외처리가 불가능하기 때문에 추가 수정이 필요하다.

 

그래서 우리는 빈 배열인 경우 never, 그렇지 않은 경우에는 T의 첫 번째 원소인 T[0]을 반환하면 된다.

 

Solution 1 (필자의 가장 근접한 접근방법)

T가 빈 배열인 경우 never를 반환하고 빈 배열이 아니면 첫 번째 원소를 반환하게 코드를 작성했다. extends를 통해 T가 빈 배열이라고 명시를 하는 것이 중요하다.

Solution 2

T["length"] 를 통해 T 배열의 길이가 0인지 아닌지 확인해서 그에 맞는 값을 Return 해준다.

Solution 3 (필자에게 생소함)

infer 키워드를 사용해서 구현할 수 있다. 그리고 spread operator를 통해서 첫 번째 원소와 나머지 원소들을 분리하는데 이 때 첫 번째 원소 부분에 infer 키워드를 통해 추론 가능한 값이면 첫 번째 원소(U)를 바로 반환하고 그렇지 않은 경우에는 never를 반환한다.

 

새로 알게된 점

infer

infer에 대해서는 해당 링크에서 아주아주 잘 설명되어 있다. 참고하면 좋을 것 같다 :)

 

후기

빈 배열인지 어떻게 확인하지?  라는 고민을 계속했던 것 같다. 결국에는 풀지 못하긴 했지만 그래도 위의 방법으로 빈 배열임을 확인할 수 있다는 것을 알게 되었다. 생각보다 어렵지는 않았던 문제인 것 같다


- https://jaenny-dev.tistory.com/2

 

[타입챌린지/type-challenge] First of Array

문제 배열 타입 T를 받아 그 배열의 첫 번째 원소의 타입을 리턴하는 제네릭 First를 구현하세요. type arr1 = ['a', 'b', 'c'] type arr2 = [3, 2, 1] type head1 = First // expected to be 'a' type head2 = First // expected to be 3

jaenny-dev.tistory.com

- https://velog.io/@from_numpy/TypeScript-infer

 

TypeScript - infer

조건부 타입의 조건식이 참으로 평가될 때에는 infer키워드를 사용할 수 있다. 예를 들어,Element<number> extends Element<infer U>와 같은 타입을 작성하면, U타입은 number타입으로 추론(infer)된다. 이후, 참

velog.io

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

[타입챌린지 - EASY] 11 - Tuple to Object

Type Challenge 2024. 3. 8. 21:01

문제

https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.ko.md

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;
type result = TupleToObject<typeof tuple>   // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

 

풀이

type TupleToObject<T extends readonly any[]> = {
  [V in T[number]]: V
}

 

How to Solve?

  1. T extends readonly any[]: any 타입의 원소를 갖는 읽기만 가능한 배열을 조건으로 하는 T를 정의한다.
  2. [V in T[number]]: V: T는 Tuple(길이와 타입이 고정된 배열)이다. 그리고 number는 튜플을 순회할 때 사용할 수 있다.

 

새로 알게된 점

as const (const assertion)

const assertion은 상수라고 주장하는 것이다. 예를 한번 들어보자.

const ts = 'TypeScript';   // type: 'TypeScript'
let ts = 'TypeScript';   // type: string

 

 

위의 코드를 보면 차이점이 뭘까? const로 선언하면 그 값 자체를 Type으로 받아온다. 참고로 string 같이 이미 지정된 타입인 경우에는 Primitive Type이라고 하며, 할당된 특정 값 그 자체를 Type으로 취급하는 것을 Literal Type 이라고 한다.

 

const SHAPE_TYPES = {
  CIRCLE: 'circle',
  RECTANGLE: 'rectangle',
  TRIANGLE: 'triangle',
};   // { CIRCLE: string; RECTANGLE: string; TRIANGLE: string;

const TEMP_SHAPE_TYPES = {
  CIRCLE: 'circle',
  RECTANGLE: 'rectangle',
  TRIANGLE: 'triangle',
} as const;   // { readonly CIRCLE: 'circle', readonly RECTANGLE: 'rectangle' ... }

 

 

as const를 사용하면 TypeScript에게 특정 값이 변경되지 않을 것임을 알려준다. 그리고 객체의 모든 필드를 readonly로 만들고 배열인 경우 readonly 배열로 만들어주며 리터럴 값은 그 값 자체의 타입으로 추론한다.

 

그래서 위의 코드를 보면 as const를 사용하지 않으면 각 속성 값은 string으로 추론되는 것을 볼 수 있다. 이것은 circle이라는 값이 아닌 다른 string 값도 할당될 수 있다는 것이다. 하지만 as const를 사용하게 되면 type 값이 값 그 자체인 circle이 될 수 있기 때문에 다른 string 값이 할당될 수 없다.

 

후기

아 이 문제는 number를 몰라서... 못풀은 것 같다. 튜플을 어떻게 순회해야 하지? 라는 생각을 계속했는데 결국은 해결하지 못했다 ㅠ

그래도 하나 알아가서 행복!


- https://baek.dev/post/50/

 

TypeScript의 강력함: Discriminated Union과 as const 활용하기 | 아웃풋 트레이닝

유형에 따라 갖는 프로퍼티가 다른 경우 Optional Property를 만들어서 불확실한 타입체크를 주기보다는 Dsicriminated Union을 활용한 타입스크립트로 확장성있는 타입을 만들어보자.

baek.dev

- https://velog.io/@jinyoung234/Type-Challenge-

 

Type Challenge - Tuple to Object

배열(튜플)을 받아, 각 원소의 값을 key/value로 갖는 오브젝트 타입을 반환하는 타입을 구현하세요.어떻게 배열이 객체의 프로퍼티가 되는지 궁금해서 as const에 대해 찾아봤다.as const는 배열을 객

velog.io

 

iamkanguk

iamkanguk

안녕하세요! 현재 NodeJS로 백엔드 취업을 희망하고 있는 이강욱이라고 합니다. 최대한 블로그 포스팅을 보시는 분들에게 자세한 정보와 즐거움을 드리기 위해 노력하며 포스팅을 쓰고 있습니다 ㅎㅎ 읽어보시고 문제가 있거나 잘못되었다고 생각되는 부분들은 언제든 댓글로 작성해주시면 최대한 빠르게 조치하겠습니다! 감사합니다.

방명록