View on GitHub: https://tsch.js.org/30970
Sometimes you may want to determine whether a string literal is a definite type. For example, when you want to check whether the type specified as a class identifier is a fixed string literal type.
type Action<ID extends string> = { readonly id: ID };
Since it must be fixed, the following types must be determined as false.
Determine whether the given type S is a definite string literal type.
접근 방식
코드
type MyEqual<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type IsFixedStringLiteralType<S extends string> = {} extends Record<S, 1>
? false
: MyEqual<[S], S extends unknown ? [S] : never>;
코드 설명
{} extends Record<S, 1>
: S가 일반적인 string
인지 체크하는 필터 역할(동적 문자열 타입 제외)abc
, 123
등 고정된 문자열 타입은 {} extends Record<S, 1>
타입을 만족하지 않음MyEqual<[S], S extends unknown ? [S] : never>
: S가 유니온 타입인지 확인(extends unknown이 true일 경우 유니온 분해)View on GitHub: https://tsch.js.org/34007
Implement CompareArrayLength
to compare two array length(T & U).
If length of T array is greater than U, return 1;
If length of U array is greater than T, return -1;
If length of T array is equal to U, return 0.
접근 방식
코드
type CompareArrayLength<
T extends any[],
U extends any[]
> = T["length"] extends U["length"]
? 0
: T extends [infer _, ...infer TRest]
? U extends [infer _, ...infer URest]
? CompareArrayLength<TRest, URest>
: 1
: -1;
코드 설명
infer
로 하나씩 추출U
배열이 먼저 끝나면 1을 리턴T
배열이 먼저 끝나면 -1을 리턴View on GitHub: https://tsch.js.org/34857
Using a Record with union types as keys doesn't allow you to make an object with only some of them
const record: Record<"a" | "b" | "c", number> = { a: 42, b: 10 };
// error: Property 'c' is missing in type '{ a: number; b: number; }'
// but required in type 'Record<"a" | "b" | "c", number>'
Using a Partial Record with union types as keys allows you to make an object without all union members, but makes all keys and values optional, potentially leaving them undefined
const partial: Partial<Record<"a" | "b" | "c", number>> = { a: 42 };
const partialType = typeof partial; // { a?: number | undefined, b? : number | undefined, c? : number | undefined }
const operation = 0 + partial.a; // error: 'partial.a' is possibly 'undefined'
const access = partial.c; // possible, type doesn't know that there is no such key
You need to make a type that takes the best of both worlds, creates all combinations of all the types in the union, so using a key that exists in the object gives you a defined type, but using a key that exists in the union and not in the object throws an error
const best: DefinedPartial<Record<"a" | "b" | "c", number>> = { a: 42 };
const sum = 0 + best.a; // 42
const error = best.b; // error: property 'b' does not exist on type '{ a: number; }'
이 문제는 TypeScript에서 객체의 타입을 더 정확하게 제어하는 방법을 구현
현재 Record
와 Partial
을 사용할 때 각각 다른 문제가 존재함:
Record<"a" | "b" | "c", number>
를 사용하면 a
, b
, c
모두 필수로 있어야 함.Partial<Record<"a" | "b" | "c", number>>
를 사용하면 모든 키가 선택적이 되어 undefined
가 될 수 있고, 존재하지 않는 키에 접근해도 타입 에러가 발생하지 않음.이 문제의 목표는 DefinedPartial
타입을 만들어서:
예를 들어 { a: 42 }
라는 객체가 있을 때, a
는 number
타입으로 정확히 인식되고, b
나 c
에 접근하면 타입 에러가 발생해야 함.
접근 방식
코드
type Keys<T> = keyof T & string;
type Subsets<T extends string> = T extends infer U
? U extends string
? [U] | [U, ...Subsets<Exclude<T, U>>]
: never
: never;
type MergeKeys<T, K extends string[]> = K extends [
infer Head extends keyof T,
...infer Tail extends string[]
]
? { [P in Head]: T[P] } & MergeKeys<T, Tail>
: {};
type DefinedPartial<T extends object> = Subsets<Keys<T>> extends infer K
? K extends string[]
? MergeKeys<T, K>
: never
: never;
실패 이유
접근 방식
코드
type DefinedPartial<T, K extends keyof T = keyof T> = K extends any
? T | DefinedPartial<Omit<T, K>>
: never;
코드 설명
K extends any
타입을 활용해 T
의 키 값 순회T
타입과 DefinedPartial<Omit<T, K>>
타입의 유니온 타입을 만들어줌View on GitHub: https://tsch.js.org/35045
Write a type, LongestCommonPrefix
that returns the longest common prefix string amongst a tuple of strings.
If there is no common prefix, return an empty string ""
.
예시
type Common = LongestCommonPrefix<["flower", "flow", "flight"]>;
// ?^ "fl"
type Uncommon = LongestCommonPrefix<["dog", "racecar", "race"]>;
// ?^ ""
""
을 반환접근 방식
코드
type AllMatch<T extends string[], P extends string> = T extends [
infer First extends string,
...infer Rest extends string[]
]
? First extends `${P}${string}`
? AllMatch<Rest, P>
: false
: true;
type LongestCommonPrefix<
T extends string[],
First extends string = T extends [infer F extends string, ...any] ? F : "",
Prefix extends string = ""
> = First extends `${infer C}${infer Rest}`
? AllMatch<T, `${Prefix}${C}`> extends true
? LongestCommonPrefix<T, Rest, `${Prefix}${C}`>
: Prefix
: Prefix;
코드 설명
First
를 활용해 첫번째 요소 확인First
를 첫 글자와 나머지 글자로 분해Prefix
에 추가, 해당 Prefix
가 모든 문자열에 존재하는지 확인Prefix
를 반환First
의 첫 번째 글자를 제외한 나머지 글자에 대해 재귀 호출View on GitHub: https://tsch.js.org/35191
The trace of a square matrix is the sum of the elements on its main diagonal.
However, it's difficult to calculate the sum with type system.
To make things simple, let's return the elements on the main diagonal with union type.
예시
type Arr = [[1, 2], [3, 4]];
type Test = Trace<Arr>; // expected to be 1 | 4
접근 방식
코드
type Trace<T extends any[][], R = T["length"] extends 0 ? [] : T[0]> = {
[X in keyof R]: {
[Y in keyof T]: X extends Y ? T[Y][Y] : never;
};
};
실패 이유
T[Y][Y]
에러 ('Y' 형식을 인덱스 형식 'T[Y]'에 사용할 수 없습니다.)접근 방식
코드
type Trace<
T extends any[][],
IndexArr extends unknown[] = [],
R = never
> = IndexArr["length"] extends T["length"]
? R
: Trace<
T,
[...IndexArr, unknown],
R | T[IndexArr["length"]][IndexArr["length"]]
>;
코드 설명
IndexArr
타입을 활용해 행렬의 인덱스를 순회, IndexArr
의 길이가 T
의 길이와 같아지면 종료T[IndexArr["length"]][IndexArr["length"]]
타입을 활용해 대각선 요소 타입 반환View on GitHub: https://tsch.js.org/35252
Determine if the given letter is an alphabet.
접근 방식
코드
type Alphabet =
| "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
type IsAlphabet<S extends string> = S extends Alphabet ? true : false;
코드 설명