(8)

[TypeScript] T extends U ? X : Y 는 정확히 어떤 의미일까?

최근에 타입 챌린지를 풀면서 T extends A ? B : C와 같은 코드를 많이 작성했던 것 같다. 그런데 막상 개념은 모르고 그냥 이런 느낌이구나~ 라고만 알면서 문제를 풀었던 것 같아서 찝찝했는데 공부를 하던 중 좋은 글을 확인해서 공부한 내용을 블로그에 적어보려고 한다. 참고한 블로그 링크는 아래 참고자료에 첨부 할 예정이니 참고하시면 좋을 것 같다! 1. 타입은 가능한 값의 집합이다? 타입은 가능한 값의 집합이다라는 말이 있다. 이 포스팅을 쓰기 전에는 이해가 아예 안됐다. 느낌조차도 오지 않았다 ㅠ 예를 들어 never 타입은 어떤 값도 할당할 수 없는 타입이다. 그래서 공집합({})이라고 말할 수 있다. 그런데 number 타입은 정수, 소수를 할당할 수 있는 타입이다. 이 예시를 보면 충..

[타입챌린지 - EASY] 4 - Pick

지금 TS를 사용하면서 백엔드 프로젝트를 진행하고 있는데 TS에 대해 겉핥기 식으로만 알면서 프로젝트를 하다 보니까 잘 사용하지 못하는 것 같다고 판단이 되었다. NestJS 오픈카톡방 커뮤니티에서 공유받아서.. 오늘부터 Type Challenge 라는 GitHub Repository에 들어가서 하루 또는 2일에 2문제씩 문제를 조금이나마 풀어보려고 한다. 문제 - https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md 풀이 /** * (1) K는 T의 Key-Properties 이다 ==> MyPick * (2) K를 순회한다 ==> [B in K] (B는 K를 순회하면서 저장하는 변수..

[TS] 빈 객체에는 어떤 타입을 사용해야 할까요?

코드를 작성하다가 빈 객체의 타입에 대해서 갑자기 궁금해지기 시작했다. 빈 객체에는 어떤 타입을 사용해야 할지 알아보자. 1. {} (빈 객체)의 의미 타입스크립트에서 빈 객체({})는 "any non-nullish value"를 의미한다. 다시 말해 null을 제외한 모든 값들을 허용한다는 의미이다. 실제로 다음과 같이 코드를 작성하면 에러가 나지 않는다. type EmptyObject = {}; const temp1: EmptyObject = 'hello world!'; const temp2: EmptyObject = 1111; undefined 값을 넣으면 에러는 발생한다. 프로젝트 코드를 작성하면서 이 부분을 의외로 많이 실수해서 정리하게 되었다.. 2. 어떻게 사용할 것인지? (1) 아무것도 ..

[맵필로그] Strict Mode 설정 + 발생한 이슈 + tsconfig.json 일부 분석

이제 express가 아니라 NestJS를 하면서 자연스럽게 TS와 친해지려고 노력하고 있다. 최근에 Nest로 프로젝트를 하면서 tsconfig.json 파일을 만질 때가 있었는데, 정확히 이 파일이 어떤 역할을 하는지 모르고 사용을 했던 것 같다. 그리고 프로젝트를 하는 도중에 내가 작성한 소스를 한번 쭉 그냥 보고 있었는데 최대한 any 타입을 지양하고 각종 메서드나 변수 등에 타입을 꼭 달아주었다. 그런데도 몇 군데 빼먹은 곳이 있었는데 생각해보니까 얼핏 이런걸 에러로 보여주는 기능이 있는데? 라고 알고 있었는데 적용이 안되어서 내가 실수한 부분이 몇 군데 있구나 싶었다. 보니까 strict 모드가 활성화 되어있지 않은 것. 그래서 이번 포스팅에서는 strict mode를 어떻게 활성화 했는지 (..

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

[TypeScript] T extends U ? X : Y 는 정확히 어떤 의미일까?

Language/TypeScript 2024. 3. 16. 22:04

최근에 타입 챌린지를 풀면서 T extends A ? B : C와 같은 코드를 많이 작성했던 것 같다. 그런데 막상 개념은 모르고 그냥 이런 느낌이구나~ 라고만 알면서 문제를 풀었던 것 같아서 찝찝했는데 공부를 하던 중 좋은 글을 확인해서 공부한 내용을 블로그에 적어보려고 한다.

 

참고한 블로그 링크는 아래 참고자료에 첨부 할 예정이니 참고하시면 좋을 것 같다!

 

1.  타입은 가능한 값의 집합이다?

타입은 가능한 값의 집합이다라는 말이 있다. 이 포스팅을 쓰기 전에는 이해가 아예 안됐다. 느낌조차도 오지 않았다 ㅠ

 

예를 들어 never 타입은 어떤 값도 할당할 수 없는 타입이다. 그래서 공집합({})이라고 말할 수 있다. 그런데 number 타입정수, 소수를 할당할 수 있는 타입이다.

 

이 예시를 보면 충분히 이해가 가능하리라고 생각한다.

 

2.  집합 개념에서의 extends 의미 + 서브타입과 슈퍼타입

extends 하면 가장 먼저 떠오르는 단어는 상속이다. 하지만 상속이라는 개념에서 벗어나서 extends 에 대해서 알아보자. 예시 코드를 같이 보면서 알아보자.

function getKey<T extends string>(value: any, key: T) {
	console.log(value, key);
}

 

T extends string 은 무슨 의미일까? extend가 상속의 의미라고만 생각하고 있었을 때는 도저히 이해가 안되었다. T가 string의 자식 클래스다! 라고 해석할 수 있겠지만 지금 우리는 이걸 원하는 것이 아니다.

 

집합의 개념으로 넘어가보면 extend는 하위 집합의 개념이다. 즉, 자식 클래스 = 하위 집합이다. A extends B 라고 하면 A는 B의 하위 집합이다. 그래서 다시 해석해보면 T는 string의 하위 집합이라는 것이다. 그래서 T는 string 타입이 가질 수 있는 것들을 가질 수 있다는 점이다.

 

이 때 T는 단순히 문자열 뿐만 아니라 string literal types 또는 unions of string literal types 도 해당된다.

getKey('A', 'B');   // OK
getKey('A', 'B' | 'C');   // OK
getKey<'lee' | 'kim'>('A', 'B')   // NOPE
getKey<'lee' | 'kim'>('A', 'kim')   // OK
getKey<1111>('A', 'B')   // NOPE

 

우리는 A⊂B를 어떻게 해석할 수 있나? "A는 B의 부분집합이다" 라고 해석할 수 있다. 하지만 "A는 B의 서브타입이고, B는 A의 슈퍼타입이다" 라고도 해석할 수 있다. 참고하는 블로그에 따르면 타입스크립트 프로그래밍 책에서는 다음과 같이 표기한다고 한다.

  • A < B: A는 B의 서브타입이다. 그리고 B는 A의 슈퍼타입이다.
  • A <: B: A는 B와 같거나 B의 서브타입이다. 마찬가지로 B는 A와 같거나 A의 슈퍼타입이다.

 

3.  그래서 우리는  어떻게 해석할 수 있나? + 예제

T extends U ? X : Y 와 같은 타입은 조건부 타입이라고도 부른다. 조건부 타입은 타입스크립트 2.8 버전부터 사용할 수 있는 문법이다. 의미는 T <: U 이면 X를 반환하고 그렇지 않으면 Y를 반환한다는 의미이다.

 

그리고 T가 만약 Union 타입이면 분배법칙을 적용할 수 있다고 한다. Type Challenge의 Exclude 문제를 참고해보자. 문제를 보면 타입 T가 "a" | "b" | "c" 이고, U가 "a" 인데 결과값은 a를 제외한 b와 c가 나와야 한다.

 

T는 지금 유니온 타입이어서 분배법칙이 적용된다. 그렇기 때문에 T의 각 타입이 U에 할당될 수 있는지 확인하고 할당이 된다고 하면 never(공집합) 타입을, 그렇지 않으면 T를 반환하면 Exclude가 구현된다.

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

 

 

이렇게 조건부 타입에 대해서 어느정도 알아보았다. 블로그 포스팅을 쓰면서 많이 이해가 된 것 같다. 타입 챌린지 꼭꼭 풀어보면 좋을 것 같다. 하나하나씩 뭔가 가닥구가 잡혀지는 것 같은 느낌이 많이 든다.


참고자료

- https://mugglim.tistory.com/13

 

[TypeScript] "T extends U ? X : Y"에 대한 고찰

TL;DR 타입은 가능한 값의 집합이다. 서브 타입과 슈퍼 타입을 집합의 관계로 설명할 수 있다. 조건부 타입은 분배법칙을 허용한다. 타입은 집합 타입은 가능한 값의 집합이다. 타입은 가능한 값

mugglim.tistory.com

 

- https://velog.io/@feelslikemmmm/TypeScriptConditional-Type

 

[TypeScript]Conditional Type

Conditional Type

velog.io

 

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

[타입챌린지 - EASY] 4 - Pick

Type Challenge 2024. 3. 5. 11:58

지금 TS를 사용하면서 백엔드 프로젝트를 진행하고 있는데 TS에 대해 겉핥기 식으로만 알면서 프로젝트를 하다 보니까 잘 사용하지 못하는 것 같다고 판단이 되었다.

 

NestJS 오픈카톡방 커뮤니티에서 공유받아서.. 오늘부터 Type Challenge 라는 GitHub Repository에 들어가서 하루 또는 2일에 2문제씩 문제를 조금이나마 풀어보려고 한다.

 

문제

- https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.md

 

풀이

/**
 * (1) K는 T의 Key-Properties 이다 ==> MyPick<T, K extends keyof T>
 * (2) K를 순회한다 ==> [B in K] (B는 K를 순회하면서 저장하는 변수)
 * (3) T의 Key인 B에 대한 Value를 가져온다 ==> T[B]
 */
type MyPick<T, K extends keyof T> = {
  [B in K]: T[B]
};

 

How to solve?

우리가 구현할 것은 TypeScript Utility Class의 Pick 타입이다. Pick은 Pick<T, K> 형태로 사용하고, T 타입으로 부터 K 프로퍼티만 추출한다는 의미가 있다. 하지만 문제에서는 Pick을 사용하지 말라고 되어있다. 따라서 우리는 저 의미를 가지고 하나하나 따져가며 타입을 작성해야 한다.

 

(1) MyPick<T, K extends keyof T>

결론을 먼저 말하자면 제네릭 T와 K를 정의할건데, K는 T의 Key를 값으로 가지는 Type이라는 것을 의미한다.

제네릭에 대한 설명은 생략하도록 하겠다.

 

extends keyof에 대해서 설명하자면.. 

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

type PersonKeys = keyof Person;   // "id" | "name"

 

  • keyof: 타입 값에 존재하는 모든 프로퍼티의 키 값을 Union 형태로 받는다. 위의 코드를 참고하세용!
  • extends: 증말루 다양하게 쓰이는 놈이다. 하지만 여기서 이거 하나만 기억하자. extends는 부분집합이라고 생각하면 된다. K extends keyof T 이기 때문에 K라는 놈은 T의 하위타입으로 제한이 된다.

extends와 keyof를 알게 된다면 위의 내용은 쉽게 이해할 수 있을 것이라고 생각된다.

(2) [B in K]: T[B]

K에 해당하는 값들을 B라는 값으로 정의한다. 그리고 그 값들의 타입은 T[B] 이다.

  • [B in K]: for const B of K 라고 이해하면 아주 쉽다 ㅎㅎㅎ
  • T[B]: obj['key'] ==> T[keys] ==> T[B]

 

후기

이번에 처음으로 풀어봤는데... 결국 다른 분의 풀이를 참고해서 문제를 풀었다. 와.. 생각보다 어려웠고 코드를 거의 작성하지 못했다.

하지만 다른 분의 풀이를 보고는 이해하기는 너무 쉬웠다. 꾸준하게 문제를 풀면서 정진해보자..

 

그래도 Pick을 직접 이제 구현할 수 있다는 것에 뿌듯하다.

 

참고자료

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

 

타입챌린지 : 4-Pick (easy)

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

suloth.tistory.com

 

- https://medium.com/harrythegreat/typescript-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-7ae8a786fb20

 

Typescript 유틸리티 클래스 파헤치기

Utility Types Docs를 중점으로

medium.com

 

- https://velog.io/@youngkiu/in-keyof-extends-keyof

 

TypeScript - `in keyof`, `extends keyof`

in keyof : supersethttps://stackoverflow.com/questions/50214731/what-does-the-in-keyword-do-in-typescriptThe in keyword is used there as part of

velog.io

 

- https://velog.io/@dongkyun/TS-extends-%ED%82%A4%EC%9B%8C%EB%93%9C-%ED%99%9C%EC%9A%A9

 

[TS] extends 키워드 활용

타입의 확장으로 사용된다.Dog 라는 interface는 live 함수와 woof 함수 2개의 프로퍼티를 가지게 된다.제네릭 타입을 연산자 우측 타입의 하위 타입으로 제한한다.T로 전달되는 타입은 string | number 타

velog.io

 

iamkanguk

iamkanguk

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

[TS] 빈 객체에는 어떤 타입을 사용해야 할까요?

Language/TypeScript 2024. 2. 7. 18:14

코드를 작성하다가 빈 객체의 타입에 대해서 갑자기 궁금해지기 시작했다.  빈 객체에는 어떤 타입을 사용해야 할지 알아보자.

 

1.  {} (빈 객체)의 의미

타입스크립트에서 빈 객체({})는 "any non-nullish value"를 의미한다. 다시 말해 null을 제외한 모든 값들을 허용한다는 의미이다.

실제로 다음과 같이 코드를 작성하면 에러가 나지 않는다.

type EmptyObject = {};

const temp1: EmptyObject = 'hello world!';
const temp2: EmptyObject = 1111;

 

undefined 값을 넣으면 에러는 발생한다. 프로젝트 코드를 작성하면서 이 부분을 의외로 많이 실수해서 정리하게 되었다..

 

2.  어떻게 사용할 것인지?

(1) 아무것도 들어가지 않을 빈 객체를 의미하고 싶을 때

Record<string, never>를 사용하면 된다. Property 값에 never를 지정하면서 아무 값도 들어가지 않을 객체라고 알려준다.

참고로 Record 타입은 Record<Key, Value> 형태로 사용하면서 키가 Key 타입이고, 값이 Value 타입인 객체 타입을 생성한다.

(2) 객체를 의미하는 경우

Record<string, unknown>를 사용하면 된다. Property에 unknown을 지정하면서 모든 타입의 값이 할당될 수 있다는 객체임을 명시해준다.


참고자료

- https://blog.hoseung.me/2021-09-15-typescript-empty-object

 

타입스크립트의 {} 타입

'빈 객체'를 타입으로 정의하는 방법을 알아봅시다.

blog.hoseung.me

- https://db2dev.tistory.com/entry/TS-%EB%B9%88-%EA%B0%9D%EC%B2%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EC%98%AC%EB%B0%94%EB%A5%B8-%ED%83%80%EC%9E%85

 

[TS] 빈 객체에 대한 올바른 타입

{} 을 타입으로 사용하지 않기 빈 객체에 {} 타입을 지정하면 @typescript-eslint/ban-types 경고가 발생한다. {} 타입은 null이 아닌 모든 값이라는 뜻이기 때문에 타입을 지정했음에도 컴파일 때 이 값이

db2dev.tistory.com

 

iamkanguk

iamkanguk

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

[맵필로그] Strict Mode 설정 + 발생한 이슈 + tsconfig.json 일부 분석

사이드 프로젝트/맵필로그 2024. 1. 22. 19:03

이제 express가 아니라 NestJS를 하면서 자연스럽게 TS와 친해지려고 노력하고 있다. 최근에 Nest로 프로젝트를 하면서 tsconfig.json 파일을 만질 때가 있었는데, 정확히 이 파일이 어떤 역할을 하는지 모르고 사용을 했던 것 같다.

 

그리고 프로젝트를 하는 도중에 내가 작성한 소스를 한번 쭉 그냥 보고 있었는데 최대한 any 타입을 지양하고 각종 메서드나 변수 등에 타입을 꼭 달아주었다. 그런데도 몇 군데 빼먹은 곳이 있었는데 생각해보니까 얼핏 이런걸 에러로 보여주는 기능이 있는데? 라고 알고 있었는데 적용이 안되어서 내가 실수한 부분이 몇 군데 있구나 싶었다.

 

보니까 strict 모드가 활성화 되어있지 않은 것. 그래서 이번 포스팅에서는 strict mode를 어떻게 활성화 했는지 (아주간단), strict mode를 키면서 에러 난거 하나 설명하기, 마지막으로 tsconfig.json 파일을 간단하게? 옵션 설명을 하는 글을 적어보려고 한다.

 

1.  Strict 모드 활성화 (strict: true)

Strict 모드는 tsconfig.json 파일에서 설정할 수 있다. Strict 모드는 모든 엄격한 유형 검사 옵션을 의미하는데 이 옵션을 사용하지 않는 것은 TS를 쓰지 않는 것이라고 할 수 있다. (왜 지금까지 안쓰고 있었을까...!)

 

{
  "compilerOptions": {
     "strict": true
  }
}

 

이 strict만 true로 설정하면 strict mode family 프로퍼티들이 전부 true로 설정된다. 하지만.. 안그래도 지금 이거 strict를 true로 설정하니까 각종 빨간줄이 엄청 많이 나오는데.. 하나씩 해결하기 넘 귀찮다...

 

만약에 이런 빨간줄이 귀찮다 ==> 선택적으로 몇 개는 false로 설정하면 될 것 같다.

 

  1. alwaysStrict --> 엄격모드에서 구문 분석 후 각 소스 파일에 "use strict" 코드 삽입
  2. strictNullChecks --> null 및 undefined 값에 대한 유형을 조정하는 옵션
  3. strictBindCallApply --> bind, call, apply 사용할 때 더욱 엄격하게 검사하는 옵션
  4. strictFunctionTypes --> 엄격한 함수 유형검사 사용
  5. strictPropertyInitialization --> 정의되지 않은 클래스의 속성이 생성자에서 초기화되었는지 확인하는 옵션
  6. noImplictAny --> 명시적이지 않은 any 타입이 지정될 경우 에러 발생
  7. noImplictThis --> 암시적 any 유형이 있는 this 표현식에서 오류 발생

위 7개가 프로퍼티들이다. 각 옵션에 대해서 설명은 다른 블로그를 통해 확인하시면 좋을 것 같다!

 

2.  Strict mode를 적용하면서 발생한 이슈 하나 공유!

has no initializer and is not definitely assigned in the constructor

 

타입 선언 후 초기값을 설정하지 않아서 생긴 문제이다. 이 문제는 어떻게 해결할 수 있을까?

 

  • strictPropertyInitialization을 false로 설정한다.
  • 확정 할당 어선셜 연산자 사용 -> !와 ?는 TypeScript에서 확정 할당 어선셜이라고 부른다. !는 컴파일러에게 선언은 하지 않았지만 무조건 값이 있다고 생각하자 라는 뜻이다. 그리고 ?는 필수가 아닌 속성이라고 알려주는 것이다.
  • 초기값 선언하기

 

3.  tsconfig.json 일부 분석

내가 궁금했던 옵션들 + 자주 적용한다는 옵션 위주로 간단하게 설명을 해보려고 한다. 두고두고 까먹을때마다 보려고 하니 참고해주길!

(1) skipLibCheck

선언 파일 유형 검사를 스킵할지에 대한 여부를 설정할 수 있는 옵션이다. 당연히 타입스크립트를 쓰니까 체킹을 해야하지 않아? 라고 생각할 수 있지만 만약에 프로젝트 규모가 크다면 상당한 시간이 소모될 수 있다. 그래서 이 옵션을 true로 설정하여 파일 타입 체크를 생략해서 컴파일 시간을 줄여줄 수 있다.

(2) declaration

이 옵션을 true로 설정하면 TS 파일을 JS 파일로 컴파일하는 과정에서 JS파일과 함께 d.ts 파일이 생성되게 한다.

보통 객체랑 함수를 쓰다보면 타입을 커스텀하는 경우가 생길텐데 이 때 타입들을 ts파일에 넣어도 되지만 용도가 다르기 때문에 분리하는 것이 좋다. 이럴 때 d.ts 파일을 만들어서 따로 타입만 관리하는데 사용되는 옵션이다.

(3) removeComments

컴파일 시 타입스크립트 소스의 주석을 모두 제거하는 것을 설정하는 옵션이다.

(4) experimentalDecorators / emitDecoratorMetadata

타입스크립트의 @Decorator를 사용하기 위해서는 true로 적용해야 작동한다.

(5) forceConsistentCasingInFileNames

파일의 이름을 대소문자 판별하게 하는 옵션이다. 프로그래밍 세계에서는 같은 알파벳이라도 대소문자를 모두 구분하기 때문에 가능한 true로 설정해서 사용하는 것이 좋다.

(6) resolveJsonModule

확장자가 .json인 모듈의 import를 허용하는 설정이다. 예를 들어 { name: "홍길동" } 이라는 JSON 데이터가 저장된 test.json이 있다고 가정해보자. TS파일에서 " import settings from './test.json'; " 으로 import를 하려고 하면 에러가 발생한다. 이 때 이 옵션을 true로 설정하게 되면 import가 가능해진다.

(7) sourceMap

컴파일 된 파일 디렉터리에 .js.map 파일이 만들어진다. 이 파일은 변환된 js 코드가 ts의 어디에 해당하는지 알려준다. 보통 디버깅에서 많이 사용한다고 한다.

 

실제로 타입스크립트 프로젝트를 배포하고 브라우저에서 개발자 모드를 이용해서 source 탭에서 보면 브라우저는 js만 인식하지만 매핑 파일에 의해 ts 파일을 인식하는 것을 볼 수 있다고 한다.

(8) noFallthroughCasesInSwitch

switch 문이 이상하면 에러를 내주는 옵션이다. 예를 들어서 switch 문에서 비어있지 않은 case라면 반드시 break 또는 return으로 case를 종료시키도록 에러를 내준다. 우리는 이를 통해서 의도치 않은 false through case에 의한 버그를 예방할 수 있다.

(9) baseUrl / paths

import 구문의 모듈 해석 시에 기준이 되는 경로를 지정한다.

노드 패키지는 따로 경로 없이 import를 하고 직접 만든 소스파일은 경로를 적어준다. 참고로 노드 패키지는 node_modules가 보통 프로젝트 최상단 경로에 있기 때문에 자동으로 인식을 해줘서 따로 경로를 적지 않아도 된다.

 

보통 상대경로로 import를 하는데 만약에 다른 경로에 파일을 만들어서 동일한 모듈을 import 하려고 한다면 위치 기준점이 달라져서 번거로울 수 있다. 그래서 우리는 baseUrl과 path 속성을 통해 절대경로로 import 한다.

 

baseUrl 속성에는 기본 경로로 설정해주고 아래에 paths 속성에 대해 절대경로를 지정하고 싶은 경로들을 지정해주면 된다.

{
  "compilerOptions": {
    "baseUrl": "./", // 절대 경로 모듈이 아닌, 모듈이 기본적으로 위치한 디렉토리 설정
    "paths": { // 'baseUrl'을 기준으로 상대 위치로 가져오기를 다시 매핑하는 항목 설정
      "@components/*": [
        "src/components/*" // import {} from '@components/파일' 할때 어느 경로로 찾아들어갈지 paths 설정
      ],
      "@utils/*": [
        "src/utils/*"
      ],
    },
    "outDir": "./dist", // 컴파일할때 js 파일 위치 경로 지정
  },
}

 

하지만 ts-node를 통해 소스파일을 실행하면 오류가 난다. 이유는 tsconfig.json 설정은 경로 alias만 준거지 실제 경로를 바꾼게 아니다. 그래서 tsconfig-paths와 tsc-alias 라는 모듈을 설치해야 한다.

(10) include

프로젝트에서 컴파일할 파일들을 지정하는 속성이다. 보통 와일드카드 패턴으로 지정을 해준다.

  • *: 해당 디렉토리에 있는 모든 파일
  • ?: 해당 디렉토리에 있는 파일들의 이름 중 한 글자라도 포함하면 해당
  • **: 해당 디렉토리의 하위 디렉토리의 모든 파일을 포함

참고자료

- https://velog.io/@gingaminga/%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-has-no-initializer-and-is-not-definitely-assigned-in-the-constructor

 

has no initializer and is not definitely assigned in the constructor

Typescript가 나를 괴롭히는구나..!

velog.io

- https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-tsconfigjson-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-%EC%B4%9D%EC%A0%95%EB%A6%AC#alwaysstrict

 

📘 타입스크립트 컴파일 설정 - tsconfig 옵션 총정리

타입스크립트 컴파일 설정 tsconfig.json은 타입스크립트를 자바스크립트로 변환 시키는 컴파일 설정을 한꺼번에 정의 해놓는 파일이라고 보면 된다. 프로젝트를 컴파일 하는데 필요한 루트 파일,

inpa.tistory.com

 

iamkanguk

iamkanguk

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

방명록