typescript系列-TypeChallenge-简单

题目链接:https://github.com/type-challenges/type-challenges/blob/master/README.zh-CN.md

如何练习提高自己的类型体操水平

正确区分类型(type)和值(value)

熟悉常见的类型体操动作

  • 原始类型(Primitive Type)
  • 字面量类型(Literal Type,Template Literal Type)
  • 联合类型(Union Type)和交叉类型(Intersection Type)
  • 索引类型(Index Type)和映射类型(Map Type)
  • 条件类型(Conditional Type)
  • typeof 和 keyof

实现 Pick

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.zh-CN.md

Pick<Type, Keys>:通过从类型中挑选一组属性键(字符串文字或字符串文字联合)来构建一种类型。

用法

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

使用其他方式实现

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}
// =========================================
// 实现 MyPick
type MyPick<T, U extends keyof T> = {
  [key in U]: T[key];
}

实现 Readonly

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/7-easy-readonly/README.zh-CN.md

Readonly<Type>:具有类型为 ReadOnly 的类型的所有属性的构造,表示属性无法再分配。

用法

interface Todo {
  title: string;
}
 
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};
 
todo.title = "Hello"; // Cannot assign to 'title' because it is a read-only property.

其他方式实现

Readonly 会接收一个 泛型参数,并返回一个完全一样的类型,只是所有属性都会被 readonly 所修饰。

也就是不可以再对该对象的属性赋值。

interface Todo {
  title: string
  description: string
}

const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar"
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

// ========================================================
// 实现 MyReadonly
type MyReadonly<T> = {
  readonly [key in keyof T]: T[key]
}

元组转换为对象

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/11-easy-tuple-to-object/README.zh-CN.md

传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
  
// =====================================================
// 实现 TupleToObject
type TupleToObject<T extends readonly any[]> = {
  [key in T[number]]: key
}

第一个元素

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/14-easy-first/README.zh-CN.md

实现一个通用First<T>,它接受一个数组T并返回它的第一个元素的类型。

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3

// ======================================================
// 实现 First
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]

获取数组长度

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/14-easy-first/README.zh-CN.md

创建一个通用的Length,接受一个readonly的数组,返回这个数组的长度。

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5

// =======================================================
// 实现 Length
type Length<T extends readonly any[]> = T['length']

实现 Exclude

题目链接:https://github.com/type-challenges/type-challenges/edit/master/questions/43-easy-exclude/README.zh-CN.md

Exclude<UnionType, ExcludedMembers>:从 UnionType 中找出不是 ExcludedMembers 中的属性,即取差集。

用法

type T0 = Exclude<"a" | "b" | "c", "a">;
     
type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
     
type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
     
type T2 = string | number

其他方式实现

实现内置的Exclude <T,U>类型,但不能直接使用它本身。

从联合类型T中排除U的类型成员,来构造一个新的类型。

type MyExclude<T, U> = T extends U ? never : T

Awaited

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/189-easy-awaited/README.zh-CN.md

假如我们有一个 Promise 对象,这个 Promise 对象会返回一个类型。在 TS 中,我们用 Promise 中的 T 来描述这个 Promise 返回的类型。请你实现一个类型,可以获取这个类型。

比如:Promise<ExampleType>,请你返回 ExampleType 类型。

// 实现
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T

// 使用
MyAwaited<Promise<string>> // string
MyAwaited<Promise<Promise<string | number>>> // string | number

参考链接:

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

infer 关键词的用法:推断出扁平的元素类型

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;
 
type Num = GetReturnType<() => number>; // type Num = number
 
type Str = GetReturnType<(x: string) => string>; // type Str = string
 
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>; // type Bools = boolean[]

If

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/268-easy-if/README.zh-CN.md

实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 FC 只能是 true 或者 falseTF 可以是任意类型。

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

// ================================================
// 实现
type If<C extends boolean, T, F> = C extends true ? T : F

Concat

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/533-easy-concat/README.zh-CN.md

在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

type Result = Concat<[1], [2]> // expected to be [1, 2]

// =================================================
// 实现
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];

Includes

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/898-easy-includes/README.zh-CN.md

在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

// =================================================
// 实现,借助使用 Equal 的方法实现 Includes
type Includes<T extends readonly unknown[], U> = 
  T extends [infer Fisrt, ...infer Rest]
    ? Equal<Fisrt, U> extends true
      ? true
      : Includes<Rest, U>
    : false;

Array.includes 在 ts 中不支持,需要写 ts 工具函数进行转换

参考文章:https://fettblog.eu/typescript-array-includes/

const ArrayUtils = {
  includes<T extends U, U>(arr: ReadonlyArray<T>, el: U): el is T {
    return arr.includes(el as T);
  },
} as const;

Push

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/3057-easy-push/README.zh-CN.md

在类型系统里实现通用的 Array.push

举例如下,

type Result = Push<[1, 2], '3'> // [1, 2, '3']

// ==============================================
// 实现
type Push<T extends readonly unknown[], U> = [...T, U];

Unshift

题目链接:https://github.com/type-challenges/type-challenges/blob/master/questions/3060-easy-unshift/README.zh-CN.md

实现类型版本的 Array.unshift

举例,

type Result = Unshift<[1, 2], 0> // [0, 1, 2,]

// ==============================================
// 实现
type Unshift<T extends readonly unknown[], U> = [U, ...T];

Parameters

实现内置的 Parameters 类型,而不是直接使用它,可参考TypeScript官方文档

Parameters<type>:将函数的参数使用元组类型返回。

用法

declare function f1(arg: { a: number; b: string }): void;
 
type T0 = Parameters<() => string>;
     // type T0 = []
type T1 = Parameters<(s: string) => void>;
     // type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
     // type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
     // type T3 = [arg: {
     //     a: number;
     //     b: string;
     // }]
type T4 = Parameters<any>;
     // type T4 = unknown[]
type T5 = Parameters<never>;
     // type T5 = never

其他实现方式

// 实现
type Parameters<T extends (...args: unknown[]) => unknown> =
	T extends (...args: infer Args) => any ? Args : never;