Typescript challenge practice series(5)
Aaron Zhang Author

logo

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

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types

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

 Comments