00002 Medium Return Type
https://github.com/type-challenges/type-challenges/tree/main/questions/00002-medium-return-type
Implement the built-in ReturnType<T>
generic without using it.
For example
const fn = (v: boolean) => {
if (v) return 1;
else return 2;
};
type a = MyReturnType<typeof fn>; // should be "1 | 2"
Tests
type cases = [
Expect<Equal<string, MyReturnType<() => string>>>,
Expect<Equal<123, MyReturnType<() => 123>>>,
Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
Expect<Equal<() => "foo", MyReturnType<() => () => "foo">>>,
Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>
];
type ComplexObject = {
a: [12, "foo"];
bar: "hello";
prev(): number;
};
const fn = (v: boolean) => (v ? 1 : 2);
const fn1 = (v: boolean, w: any) => (v ? 1 : 2);
Solutions
We need to use infer
to get the returning type of the function.
type MyReturnType<T> = T extends () => infer R ? R : never;
This will pass most of the tests, but not for those functions with parameters. So we need to specify parameters for the function type.
type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;
Reference
00003 Medium Omit
https://github.com/type-challenges/type-challenges/tree/main/questions/00003-medium-omit
Implement the built-in Omit<T, K>
generic without using it.
Constructs a type by picking all properties from T and then removing K
For example
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = MyOmit<Todo, "description" | "title">;
const todo: TodoPreview = {
completed: false,
};
Tests
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;
}
Solution
To solve this one, we’ll need to use Exclude
to remove the specified keys and get the rest key with their value in type.
type MyOmit<T, K extends keyof T> = {
[KEY in Exclude<keyof T, K>]: T[KEY];
};
Exclude<keyof T, K>
returns the remaining keys, and we can just use mapped types KEY in TYPES
to get all remaining keys.
Reference
https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys
https://www.typescriptlang.org/docs/handbook/utility-types.html#excludeuniontype-excludedmembers
00008 Medium Readonly 2
https://github.com/type-challenges/type-challenges/tree/main/questions/00008-medium-readonly-2
Implement a generic MyReadonly2<T, K>
which takes two type argument T
and K
.
K
specify the set of properties of T
that should set to Readonly. When K
is not provided, it should make all properties readonly just like the normal Readonly<T>
.
For example
interface Todo {
title: string;
description: string;
completed: boolean;
}
const todo: MyReadonly2<Todo, "title" | "description"> = {
title: "Hey",
description: "foobar",
completed: false,
};
todo.title = "Hello"; // Error: cannot reassign a readonly property
todo.description = "barFoo"; // Error: cannot reassign a readonly property
todo.completed = true; // OK
Tests
import type { Alike, Expect } from "@type-challenges/utils";
type cases = [
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, "title" | "description">, Expected>>,
Expect<Alike<MyReadonly2<Todo2, "title" | "description">, Expected>>
];
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;
}
Solution
From the example we know that we need to implement fields specified in K
to be readonly. The idea is to separate those specified fields from the others and add readonly
operator.
We can use omit to get those non-readonly properties via Omit
type MyReadonly2<T, K extends keyof T> = Omit<T, K> & {
readonly [KEY in K]: T[KEY];
};
This would mostly make tests pass, except for the first one, which hasn’t specified any K
. This case we would make all fields readonly. So it would be equivalent as we specified all of its properties. So we need to give it a default value.
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & {
readonly [KEY in K]: T[KEY];
};
Summary
👉 Default generic type
Generic type can have a default type just like Javascript.
👉 Union types via &
:
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#unions
👉 Omit
:
https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys