(4)

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

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉 작성해보려고 한다.아직 100% 완벽하게 이해하지 못해서 주변에 자문을 많이 구해보려고 한다 (명확한 답안을 찾지 못함) https://github.com/type-challenges/type-challenges/blob/main/questions/00599-medium-merge/README.md type-challenges/questions/00599-medium-merge/README.md at main · type-challenges/type-challengesCollec..

[JavaScript] 배열 메서드 Push를 직접 구현해보자!

유튜브 코딩문TV님이 JavaScript 배열 메서드를 직접 구현해보라는 영상을 올려주셨다. 흥미가 생겨서 하나씩 직접 구현을 해보고 영상이랑 비교해보고 싶었다. 그러는 도중 Push 메서드에서 살짝 막혀서 그 과정과 내가 배운 내용에 대해서 한번 정리해보려고 한다. Push 메서드? JavaScript에서 Push 메서드는 배열의 끝에 하나 이상의 요소를 추가하고, 배열의 새로운 길이를 반환한다. 그리고 하나의 요소가 아닌 여러개의 요소를 Push 할 수 있다. (ex. .push(1,2,3,4)) 참고로 필자는 Push 메서드가 새로운 배열의 길이를 반환하는지 몰랐다....! MDN에서는 예제와 함께 설명하니 참고하면 좋을 것 같다! 필자가 구현한 Push 메서드 (형편없음) function push..

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

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

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

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

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

Language/TypeScript 2024. 10. 24. 10:53

Type Challenge를 풀어본 사람이라면 Equal Type을 Type Challenge 문제에서 유틸 함수로 제공을 해준다는 것을 알 수 있을 것이다.

필자는 해당 문제를 풀면서 여러가지 궁금한 점이 있어서 이렇게 글을 쭉 작성해보려고 한다.

아직 100% 완벽하게 이해하지 못해서 주변에 자문을 많이 구해보려고 한다 (명확한 답안을 찾지 못함)

 

https://github.com/type-challenges/type-challenges/blob/main/questions/00599-medium-merge/README.md

 

type-challenges/questions/00599-medium-merge/README.md at main · type-challenges/type-challenges

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

github.com

 

해당 문제를 처음에 필자는 다음과 같이 풀었다.

type Merge<F, S> = Omit<F, keyof S> & S

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

type Foo = {
  a: number
  b: string
}
type Bar = {
  b: number
  c: boolean
}

type cases = [
  Expect<Equal<Merge<Foo, Bar>, {
    a: number
    b: number
    c: boolean
  }>>
]

 

S의 Key들을 F에서 제외시키고 S와 Intersection을 하면 문제가 해결될 것이라고 생각했지만 Playground에서는 에러를 발생시키고 있었다. 그래서 도저히 왜 에러가 발생하는지 이해가 되지 않았고 다른 사람이 작성한 답안을 같이 확인해봤다.

type Merge<F, S> = {
	[key in keyof F | keyof S]: key extends keyof S
    	? S[key]
        : key extends keyof F
        	? F[key]
            : never;
};

 

위 코드의 Merge 타입은 정상적으로 Playground에서 동작을 하고 있다. 왜 이 Merge 타입은 통과를 하는 것인지 이해가 되지 않았다.

(물론 위 Merge 타입이 잘 작성이 되었다는 것은 이해했다. 하지만 Omit 타입을 사용한 것도 잘 작성이 되었다고 생각했다)

 

그래서 필자는 이 시점에서 2가지 생각이 들었다.

1. 필자가 Omit 타입을 사용해 작성한 답안이 애초에 틀린 답안인 경우

2. Type Challenge에서 제공하는 유틸 타입인 Equal 타입이 잘못된 경우

 

위 두 케이스를 각각 알아보도록 하자.

1.  필자가 Omit 타입을 사용해 작성한 답안이 잘못된 경우

내가 Omit 타입을 잘못 사용했나? 라는 생각이 들어 다시 한번 답안을 확인했지만 내 머리로는 특별히 로직상 문제가 없는 것 같다고 판단했다. 

 

그리고 해당 답이 이슈 탭에서 가장 많은 따봉을 받은 답이었어서 특별히 문제가 되는 답은 아닐거라고 생각했다.

근데 나는 왜 틀리다고 나오지.. 뭔가 나랑 다르니까 22명의 사람들이 따봉을 누른게 아닐까?

그래서 어쩌면 이 사람들은 Playground에서 작업하지 않고 직접 구현한 Equal 타입을 사용하는게 아닐까라는 약간의 의심을 해봤다.

2.  Type Challenge에서 제공하는 유틸 타입인 Equal 타입이 잘못된 경우

먼저 확인해보니 Equal Type에 대한 이야기가 많은 것 같다.

아래는 TypeScript 공식 GitHub의 Issue로 올라온 내용이다.

 

https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650

 

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript

Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check type equivalence. I want a...

github.com

 

Type Challenge에서 유틸성 타입으로 사용된 Equal 타입은 아래와 같다.

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

 

위의 타입을 어떻게 이해해야 할까? 아직 이해를 100% 하지는 못했지만..

아래 두 링크를 참고하면 이해할 수 있을 것이라고 생각된다.

 

결론만 말하면 X extends Y ? true : false 랑 비슷하지만 차이점을 두기 위한 것이라고 생각하자.

true extends boolean은 true이지만 Equal 하지는 않은 것이 사실이다. Equal 하지 않다는 것을 표현하기 위해 저 기법을 사용했다고 지금은 그렇게 생각하자 (더욱 깊은 수준에서 엄격하게 타입을 비교할 수 있다?)

 

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

 

타입챌린지 : Equal - 번외

이 글은 제가 타입챌린지를 하면서 해석한 내용을 적는 글입니다. 틀린 내용이 있으면 댓글 달아주시면 감사하겠습니다. 타입챌린지에서 항상 사용하는 타입이 있다. 그것은 바로 Equal. 그래서

suloth.tistory.com

- https://kscodebase.tistory.com/643

 

Equal type 설명하기

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check ty

kscodebase.tistory.com

 

위의 코드가 대충 어떤 식으로 구현이 되었는지를 파악했다면 이제 저 Equal 타입이 과연 잘못된 타입일까? 를 의심해봐야 한다.

결론적으로는 잘못된 건 아니지만 100% 정확한지는 모른다는 것이다.

 

- https://stackoverflow.com/questions/68961864/how-does-the-equals-work-in-typescript/68963796#68963796

 

How does the `Equals` work in typescript?

I found an Equals utils mentioned at: https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650 export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extend...

stackoverflow.com

 

위 StackOverflow를 보면 Equal 타입에 대해 굉장히 잘 분석해준 답변이 있다. 시간 내서 천천히 해석하면서 읽어보는 것을 강추한다.

해당 내용을 보면 왜 Type Challenge에서 { x: 1 } & { y: 2 } 와 { x: 1, y: 2 } 가 왜 Equal 하지 않다는 것이 이해가 될 것이다.
(물론 이것도 100% 정답은 아닐 수 있다는 가정이다)

 

지금부터 조금 길 수도 있지만 최대한 짧게 Stack Overflow에 올라온 답변에 대해 핵심만 정리를 해보려고 한다.

리마인드 차원에서 다시 코드를 여기에 작성해보고 시작하도록 하겠다.

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

(1) 해당 타입은 두 개의 함수 타입을 비교해서 동등한 지 확인하는 것이다.

위 문법은 화살표 함수에 제네릭을 입힌 것이라고 보면 된다. 해당 문법이 이해가 되지 않는다면 아래 타입스크립트 핸드북을 읽어보자.

https://www.typescriptlang.org/docs/handbook/2/generics.html

 

여기 지점에서 필자의 고민과 동일하게 왜 저렇게 함수 타입을 비교하는 걸까? 다른 방법은 없을까? 라고 생각할 수 있다.

필자도 100% 이해한 건 아니지만, X extends Y 와 같은 기법으로 대체해서 사용하면 문제가 되는 부분이 발생하기 때문에 조금 더 엄격하게 비교하기 위한 기법이다! 라고 정도만 이해를 하고 넘어갔다.

 

몇 가지 예시를 같이 들어보자.

declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends number ? 1 : 2)
y = x // Should this be an error or not?

 

x와 y 변수에 다음과 같이 타입을 할당했다. 우리는 number 타입과 number 타입이 동일한지를 보고 싶은 것이다.

그리고 y에 x를 할당할 수 있는지를 확인했다. 이것이 우리가 원하는 Equal 로직이 아닌가?

위의 코드를 보면 Type Checker가 정상적으로 인식을 한다.

 

이번에는 number 타입과 string 타입을 비교해보자.

declare let x: <T>() => (T extends /*1st*/ number ? 1 : 2)
declare let y: <T>() => (T extends /*3rd*/ string ? 1 : 2)
y = x // It will occur error!

 

 

 

위의 코드는 이전 예시와 다르게 체커가 에러를 뱉을 것이다. 왜냐하면 x의 함수 타입과 y의 함수 타입이 불일치하기 때문이다.

위의 내용에 살을 조금 더 붙여보자. 아래 코드에서 주석으로 붙은 번호 순서대로 설명을 해보겠다.

declare let x: <T>() => (T extends number ? 1 : 2)
declare let y: <T>() => (T extends string ? 1 : 2)

const a = x<string>() // --- (1)
const b = x<number>() // --- (2)

const c = y<string>() // --- (3)
const d = y<number>() // --- (4)

y = x; --- (5)

const e = y<string>(); --- (6)
const f = y<number>(); --- (7)

 

1. 제네릭에 string 타입을 할당했기 때문에 string extends number가 수행될 것이다. string이 number에 할당이 가능하지 않기 때문에 2가 저장될 것이다.

2번-4번: 1번 내용을 이해했다면 충분히 해결할 수 있을 것이라고 생각한다.

5. x와 y가 동일한지 비교하기 위해 y에 x를 할당해보는 것이다. 아마 에러가 발생할 것이다. 그렇지만 일단 넘어가자.

6. y에 x를 할당했다고 가정해보자. 그러면 결국 x<string>()과 동일하다. y에 x가 정상적으로 할당되었다고 한다면 x<string>()과 y<string>()의 결과는 동일해야 한다. 하지만 x<string>()의 결과는 2, y<string>()의 결과는 1로 동일하지 않다.

7. 6번의 내용을 이해하면 이해할 수 있을 것이다.

 

Stack Overflow 답변 작성자님은 위와 같은 이유 때문에 extends 뒤에 오는 X와 Y가 동일해야 정상적인 결과가 반환되는 것이라고 말하고 있는 것이다.

 

(2) 결국 우리가 궁금한 것은 왜 { x: 1 } & { y: 2 } 그리고 {x : 1, y: 2} 를 비교했을 때 false 라는 거야?

결론만 말하자면 flag 비교로 인해 false가 발생한다는 것이다.

아래 코드는 TypeScript에서 수행하는 Type Checker의 소스 중 일부이다.

function isTypeRelatedTo(source: Type, target: Type, relation: /* ... */) {
    // ...
    if (source === target) {
        return true;
    }
    if (relation !== identityRelation) {
        // ...
    }
    else {
        if (source.flags !== target.flags) return false;
        if (source.flags & TypeFlags.Singleton) return true;
    }
    // ...
}

 

 

위의 소스코드에서 눈여겨 봐야할 점은 Type 타입이 무엇이고, flag가 의미하는게 뭐냐는 것이다.

아래 코드를 보면 이해될 것이다. TypeFlags에 Intersection 이 정의가 되어있다는 것이다.

export interface Type {
	flags: TypeFlags;   // 여기!
    symbol: Symbol;
    pattern?: DestructuringPattern;
    aliasSymbol?: Symbol;
    aliasTypeArguments?: readonly Type[];
}
    
export enum TypeFlags {
	// ...
    Object = 524288,
	Intersection = 2097152,
}

 

source에는 { x: 1 } & { y: 2 } 가 들어갔고, target에는 { x: 1, y: 2 } 가 할당되었다고 생각해보자.

그러면 위의 코드를 보면 source.flags는 intersection 타입이기 때문에 해당하는 값인 2097152가 할당될 것이고, target.flags는 단순 object literal이기 때문에 object 타입으로 524288이 할당될 것이다.

 

두 값을 비교해보면 동일하지 않기 때문에 false가 반환되는 것이고 그래서 위의 Equal 타입에서는 false가 보여지는 것이다.

(3) 그런데 아무리 생각해도 이해가 안대져?

당연하다. 이론적으로 intersection 적용한 것과 일반 object literal 타입을 비교했을 때 동일한 게 맞다.

그냥 Type Challenge에서는 이런 형태의 Equal 타입을 추구하고 있다는 것과 해당 Equal 타입은 좀 더 엄격하게? 유연하게? 타입을 비교하는 것이다 정도로만 생각해도.. 괜찮지 않을까 싶다. 더 딥하게 파고들면 굉장히 머리아프다..

 

(왜 엄격하게? 유연하게? 라고 표현했냐면 Intersection을 적용한 타입과 literal로 표기한 타입을 비교했을 때 false라고 하는 것을 엄격하다고 표현해야 하는지 유연하다 라고 표현해야 하는지를 잘 모르겠어서 이렇게 작성했다. StackOverflow에서는 lazy approach 라고 표기한다!)

 

이에 대한 추가 예시로 해당 글에 댓글로 제로초님이 unknown과 any를 비교하는 것에 대해서도 질문을 하신 것 같은데 자세한 내용은 참고하면 좋을 것 같다!

 

그리고 이거는 필자가 따로 해본건데 true와 boolean을 비교하는 것도 한번 해보길 바란ㄷr.

(boolean이 true | false로 치환되어 제네릭 타입에 할당되어 분배 조건부 타입이 적용된다는 건 처음알았음)

3.  마무리

이정도로 글을 마무리하려고 한다. 아직 100% 이해했다고 말은 못하지만, 이 정도로만 학습하려고 한다.

검토를 몇번 하긴 했지만 해당 글에서 틀린 부분이 몇 개 있을 것이라고 생각이 드는데...

 

관심이 있거나 Type Challenge를 평소에 푸는 개발자 분들은 필자가 위에 참조 걸어놓은 블로그들과 Stack Overflow 내용을 한 번은 읽어보면 좋을 것 같다고 생각한다.

 

언제든 따끔한 피드백은 환영입니다! 감사합니다 :)


 

필자가 올린 issue입니다. 아마 댓글이 안달릴 것 같지만 이 포스팅을 보시는 개발자 분들 중에 흥미가 있다면 한번 필자의 글을 읽어보고 답을 달아주시면 대단히 감사하겠습니다!_!

https://github.com/type-challenges/type-challenges/issues/34874

 

Difference between the Equal Type implemented in the Type Challenge Repository and the Equal Type in this issue. · Issue #34874

I have a question while solving the Merge problem of the medium level. Here is the source code I implemented the Merge type. type Merge<F, S> = Omit<F, keyof S> & S /* _____________ Test Cases ____...

github.com

 

참고자료

 

- https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650

 

[Feature request]type level equal operator · Issue #27024 · microsoft/TypeScript

Search Terms Type System Equal Suggestion T1 == T2 Use Cases TypeScript type system is highly functional. Type level testing is required. However, we can not easily check type equivalence. I want a...

github.com

 

iamkanguk

iamkanguk

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

[JavaScript] 배열 메서드 Push를 직접 구현해보자!

Language/JavaScript 2024. 3. 31. 22:47

유튜브 코딩문TV님이 JavaScript 배열 메서드를 직접 구현해보라는 영상을 올려주셨다.

흥미가 생겨서 하나씩 직접 구현을 해보고 영상이랑 비교해보고 싶었다.

그러는 도중 Push 메서드에서 살짝 막혀서 그 과정과 내가 배운 내용에 대해서 한번 정리해보려고 한다.

 

Push 메서드?

JavaScript에서 Push 메서드는 배열의 끝에 하나 이상의 요소를 추가하고, 배열의 새로운 길이를 반환한다.

그리고 하나의 요소가 아닌 여러개의 요소를 Push 할 수 있다. (ex. .push(1,2,3,4))

참고로 필자는 Push 메서드가 새로운 배열의 길이를 반환하는지 몰랐다....!

MDN에서는 예제와 함께 설명하니 참고하면 좋을 것 같다!

 

필자가 구현한 Push 메서드 (형편없음)

function pushV1(arr, ...items) {
  let idx = arr.length;

  for (const item of items) {
    arr[idx++] = item;
  }

  return arr;
}

function pushV2(arr, ...items) {
  return [...arr, ...items];
}

console.log(pushV1([1, 2, 3, 4, 5], 6, 7));
console.log(pushV2([1, 2, 3, 4, 5], 6, 7));

 

위와 같이 2개의 방법으로 작성해봤다. 하지만 실제로 우리가 사용하는 Push 메서드는 이런 형태로 사용하지 않는다. [배열].push(...args) 형태로 사용하기 때문에 우리는 수정을 해야한다.

 

수정된 Push 메서드

/**
 * 하지만 위에처럼 하는건 우리가 실제로 사용하는 배열 메서드 같지 않다.
 * 실제 배열의 Push 메서드는 길이를 반환한다.
 * 그리고 우리는 prototype을 사용해야 한다.
 */
function pushV3(...items) {
  for (let i = 0; i < items.length; i++) {
    this[this.length] = items[i];
  }

  return this.length;
}

Array.prototype.pushV3 = pushV3;

const testArray = [1, 2, 3, 4, 5];
console.log(testArray.pushV3(6, 7));   // 7
console.log(testArray);   // [1, 2, 3, 4, 5, 6, 7]

 

Prototype에 pushV3 메서드를 등록해주면서 실제로 우리가 쓰는 듯한 메서드 식으로 구현을 했다.

 

JavaScript에서 함수의 this는 함수가 호출된 컨텍스트에 따라 값이 달라지는데, obj.method()와 같이 메서드 형태로 호출되는 경우에 this는 메서드를 호출한 객체(obj)에 바인딩된다. 따라서 testArray.pushV3(...)에서 this는 testArray가 되는 것이다.

 

그리고 JavaScript에서 모든 객체는 Prototype 이라는 다른 객체를 상속받는데 프로토타입은 객체의 공통 속성과 메서드를 정의한다. Array.prototype은 모든 배열 객체가 상속받는 프로토타입 객체이다. 그래서 Array.prototype.pushV3 = pushV3는 Array 객체의 프로토타입에 pushV3 라는 메서드를 추가한다는 것이고, 이는 모든 배열 인스턴스가 pushV3 메서드를 사용할 수 있다는 것을 의미한다.


사실 이걸 몰랐다는 거에 너무 부끄럽지만 이렇게 사소한 것 하나하나 잡아가야 좋은 개발자가 될 수 있을 것이라고 생각한다.

그리고, 필자가 해당 내용에 대한 코드를 GitHub에 올리고 있는데 심심하면 한번씩 봐주시면 감사하겠습니당!

 

Push 메서드가 length를 반환한다는 것을.... 코딩문님 답글을 통해 알아버렸다는..! 허허..

 

- https://www.youtube.com/watch?v=j1Fvixz0RyQ

 

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

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

방명록