
View on GitHub: https://tsch.js.org/2
내장 제네릭 ReturnType<T>을 이를 사용하지 않고 구현하세요.
type MyReturnType<T extends Function> = T extends (...args: any) => infer U
? U
: never;
ReturnType<T>이란 T의 반환 타입을 반환하는 제네릭 타입이다.T가 Function 타입인지 확인infer U로 추론해 추론한 타입 U를 반환never 반환...args는 아무 이름이어도 되고, TypeScript에서 그냥 ‘추론할 위치’가 rest parameter임을 표시하기 위한 placeholder`(...args:any)는 되고 (args: any)는 안되는 이유는?
(...args: any) 형태는 0개 이상의 매개변수를 허용하며, 모든 매개변수는 배열 형태로 캡처(args: any) 형태는 1개의 매개변수를 허용하며, 매개변수는 단일 값으로 캡처(...args: any) 형태로 작성해야 함View on GitHub: https://tsch.js.org/3
T에서 K 프로퍼티만 제거해 새로운 오브젝트 타입을 만드는 내장 제네릭 Omit<T, K>를 이를 사용하지 않고 구현하세요.
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
K extends keyof T를 통해 K가 T의 키 값임을 명시P in keyof T를 통해 T의 키 값들을 순회as를 통해 P가 K에 포함되지 않는 경우에만 P를 반환never는 타입 시스템에서 제거 되기 때문에, P가 K에 포함되지 않는 경우에만 P: T[P]가 반환 됨
as딥 다이브
as를 활용한 키 리맵핑은 타입스크립트 4.1 버전에 추가된 기능as 절을 사용해서 매핑된 타입의 키를 다시 매핑할 수 있음type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties];
};
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property];
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
never를 생성해서 키를 필터링할 수 있음string | number | symbol 의 조합뿐만 아니라 모든 타입의 조합을 임의로 매핑할 수 있음View on GitHub: https://tsch.js.org/8
T에서 K 프로퍼티만 읽기 전용으로 설정해 새로운 오브젝트 타입을 만드는 제네릭 MyReadonly2<T, K>를 구현하세요. K가 주어지지 않으면 단순히 Readonly<T>처럼 모든 프로퍼티를 읽기 전용으로 설정해야 합니다.
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] };
as를 활용한 필터링 기능 사용K에 할당할 수 있는 T의 키 값인 P에 대해서는 readonly 키워드를 붙이고, 그렇지 않은 키 값에 대해서는 제거K에 할당할 수 있는 T의 키 값인 P에 대해서는 제거, 그렇지 않은 키 값에 대해서는 기존 타입 유지왜 두 집합을 union 타입으로 합치면 안되는걸까?
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] };
type Example = { foo: string; bar: number; baz: boolean };
type Result = MyReadonly2<Example, "foo" | "bar">;
// Result = { readonly foo: string; readonly bar: number } | { baz: boolean };
Intersection은 교집합인데, 왜 서로 다른 타입을 합치는 것처럼 보이는걸까?
첫 번째 집합과 두 번째 집합은 key가 서로 다른 객체이다.
그렇다면 intersection(교차) 타입은 두 집합의 교집합(공집합)을 반환해야 하는 것이 아닌가?
TypeScript에서 교차 타입은 교집합이 아니라 두 타입을 "결합"한 새로운 타입을 반환
두 개 이상의 타입을 조합하여 하나의 타입을 만들어 각 타입의 속성을 모두 포함하는 타입이 생성됨
type A = { foo: string };
type B = { bar: number };
type C = A & B;
// 결과 타입: { foo: string; bar: number }
type A = { foo: string };
type B = { foo: string };
type C = A & B;
// 결과: { foo: string }
type A = { foo: string };
type B = { foo: number };
type C = A & B;
// 결과: { foo: never }
type A = { foo: string };
type B = { bar: number };
type C = A & B;
// 결과: { foo: string; bar: number }
// 예시1
type A = (x: number) => string;
type B = (x: number) => number;
type C = A & B;
// C는 두 시그니처를 모두 만족해야 함
const fn: C = (x: number) => {
if (x > 0) return "string";
return 123; // 타입 에러 (반환 타입이 모두 만족하지 않음)
};
// 예시2
type A = (x: string) => number;
type B = (x: number) => string;
type C = A & B;
const fn: C = (x: any) => {
if (typeof x === "string") return x.length; // x가 string일 때 number 반환
if (typeof x === "number") return x.toString(); // x가 number일 때 string 반환
};
View on GitHub: https://tsch.js.org/9
객체의 프로퍼티와 모든 하위 객체를 재귀적으로 읽기 전용으로 설정하는 제네릭 DeepReadonly<T>를 구현하세요.
이 챌린지에서는 타입 파라미터 T를 객체 타입으로 제한하고 있습니다. 객체뿐만 아니라 배열, 함수, 클래스 등 가능한 다양한 형태의 타입 파라미터를 사용하도록 도전해 보세요.
type DeepReadonly<T> = T extends object
? T extends Function
? T
: { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
T가 객체인 경우를 체크, 객체가 아닌 경우 T를 반환T가 객체이지만 함수일 경우 T를 반환 (해당 조건이 없을 경우, 함수일 경우 빈 객체를 반환)T가 객체이고 함수가 아니라면 객체의 프로퍼티를 읽기 전용으로 설정한 타입을 반환배열이 제대로 처리되는 이유
T가 배열일 경우 { readonly [K in keyof T]: DeepReadonly<T[K]> } 타입을 반환keyof는 인덱스와 특수키(length, push 등)를 포함함.type TestArray = [1, 2, 3];
type Keys = keyof TestArray; // "0" | "1" | "2" | "length" | "push" | "pop" | "concat" | ...
View on GitHub: https://tsch.js.org/10
튜플 값으로 유니온 타입을 생성하는 제네릭 TupleToUnion<T>를 구현하세요.
type TupleToUnion<T extends readonly any[]> = T[number];
T[number]을 활용해 union 타입 반환View on GitHub: https://tsch.js.org/12
체인 가능 옵션은 일반적으로 Javascript에서 사용됩니다. 하지만 TypeScript로 전환하면 제대로 구현할 수 있나요?
이 챌린지에서는 option(key, value)와 get() 두가지 함수를 제공하는 객체(또는 클래스) 타입을 구현해야 합니다. 현재 타입을 option으로 지정된 키와 값으로 확장할 수 있고 get으로 최종 결과를 가져올 수 있어야 합니다.
예시
declare const config Chainable;
const result = config
.option("foo", 123)
.option("name", "type-challenges")
.option("bar", { value: "Hello World" })
.get();
// 결과는 다음과 같습니다:
interface Result {
foo: number;
name: string;
bar: {
value: string;
};
}
문제를 해결하기 위해 js/ts 로직을 작성할 필요는 없습니다. 단지 타입 수준입니다.
key는 string만 허용하고 value는 무엇이든 될 수 있다고 가정합니다. 같은 key는 두 번 전달되지 않습니다.
type Chainable<CurrentConfig = object> = {
option<OptionKey extends string, OptionValue extends any>(
key: Exclude<OptionKey, keyof CurrentConfig>,
value: OptionValue
): Chainable<Omit<CurrentConfig, OptionKey> & Record<OptionKey, OptionValue>>;
get(): CurrentConfig;
};
CurrentConfig를 통해 현재까지 설정된 객체를 추적CurrentConfig는 최초 타입 선언 시 빈 객체로 초기화option 함수는 새로운 key-value 쌍을 추가OptionKey는 string, OptionValue는 any로 선언Exclude<OptionKey, keyof CurrentConfig>을 통해 이미 존재하는 key를 제외Omit<CurrentConfig, OptionKey>을 통해 기존 객체에서 해당 key를 제거Record<OptionKey, OptionValue>을 통해 새로운 key-value 쌍을 추가Chainable 타입은 Omit과 Record를 통해 중복되지 않은 key를 가진 새로운 객체를 반환get 함수는 최종 결과를 반환