首页 前端 TypeScript 正文

TypeScript 泛型从新手到入门


从一些简单的例子来学习类型体操,效率更高。

所有题目来自 type-challenges简单挑战。

题目解答同步到 github

泛型可以理解成类型函数。<T> 就表示类型参数,和形参数一样,<>里写什么字母都可以。

实现 Pick

4 - 实现 Pick

by Anthony Fu (@antfu) #简单 #union #built-in

题目

实现 TS 内置的 Pick<T, K>,但不可以使用它。

从类型 T 中选择出属性 K,构造成一个新的类型

例如:

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

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

  const todo: TodoPreview = {
      title: 'Clean room',
      completed: false,
  }

知识点

  1. keyof

  • keyof 的作用将一个 类型 映射为它 所有成员名称的联合类型

    interface Todo {
    title: string;
    description: string;
    completed: boolean;
    }
    type todoKey = keyof Todo // "title" | "description" | "completed"

  • 实际使用 比如我们经常获取一个对象的某个属性,会获取到 undefined

    function getProperty(obj, key) {
    return obj[key];
    }

const obj = { foo: 1, bar: 2, baz: 3, };

const foo = getProperty(obj, "foo"); const b = getProperty(obj, "b"); console.log(b); // undefined

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}

keyof 来获取 T 的所有成员属性,用 extends 来判断 K 是否可以赋值它。

用上面的例子来分解:

// 这里泛型的意思就是,K 是 T 成员属性中的一个。如果 K 传的值不是 T 成员属性其中之一,就会报错。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

// keyof T => "foo" | "bar" | "baz"

const foo = getProperty(obj, "foo"); // "foo" extends "foo" | "bar" | "baz",正常

const b = getProperty(obj, "b");  // "b" extends "foo" | "bar" | "baz",报错

TypeScript 泛型从新手到入门  第1张 2. extends

  • extends 有很多功能,这里只讲上面用到的功能。 判断是否能将左边的类型赋值给右边的类型

  • 实际使用

    // 左边能赋值给右边
    type trueType = "foo" extends "foo" | "bar" | "baz" ? "trueType" : "falseType"
    // 左边不能赋值给右边
    type falseType = "b" extends "foo" | "bar" | "baz" ? "trueType" : "falseType"

  1. in

  • in 操作符用于遍历目标类型的公开属性名。类似 for .. in 的机制。

  • 实际使用

    // 遍历枚举类型
    enum Letter {
    A,
    B,
    C,
    }
    type LetterMap = { [key in Letter]: string };

// 遍历联合类型 type Property = "name" | "age" | "phoneNum"; type PropertyObject = { [key in Property]: string };

### 解答
3 条测试用例
1. `MyPick<Todo, 'title' | 'completed' | 'invalid'>` **K extends keyof T,限制了 K 只能是 T 的成员属性,否则报错**
2. `Expect<Equal<Expected1, MyPick<Todo, 'title'>>>`
3. `Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>`
**2 和 3 都是 U 是否是 K 的一个属性,如果是就添加。就达到了 Pick 的效果了。**
```ts
type MyPick<T, K extends keyof T> = {
  [U in K]: T[U]
}

实现 Readonly

7 - 实现 Readonly

by Anthony Fu (@antfu) #简单 #built-in #readonly #object-keys

题目

不要使用内置的Readonly<T>,自己实现一个。

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

知识点

  1. readonly

    type Foo = {
    readonly bar: number;
    readonly bas: number;
    };

// 初始化 const foo: Foo = { bar: 123, bas: 456 };

// 不能被改变 foo.bar = 456; // Error: foo.bar 为只读属性

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"0

元组转换为对象

11 - 元组转换为对象

by sinoon (@sinoon) #简单

题目

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

例如:

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"1

知识点

  1. const  字面量,可以理解为不可修改。

    interface Todo {
    title: string;
    description: string;
    completed: boolean;
    }
    type todoKey = keyof Todo // "title" | "description" | "completed"2

const a2 = '123' type a2 = typeof a2   // '123' a2 = "321"  // 无法分配到 "a2" ,因为它是常数。

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"3

  1. typeofTS 里可以理解为有两部分,一部分是原来的 JS,一部分是类型。如果要获取一个JavaScript变量的类型就可以用 typeof

    interface Todo {
    title: string;
    description: string;
    completed: boolean;
    }
    type todoKey = keyof Todo // "title" | "description" | "completed"4

// 结果:(a: number, b:number) => number type t1  = typeof add // 结果:{ name: string; age: number; } type t2 = typeof obj

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"5

解答

还和上面一样,只不过需要先用T[number]获取到元组的所有元素,
然后遍历,
因为是获取到的元素,返回值直接返回 P 就行。

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"6

注意泛型里的 T extends readonly string[],如果使用 any 会导致 @ts-expect-error 这条不通过。

@ts-expect-error,如果我们把这个注释放在代码行前面,TypeScript 就会预期下面的代码会报错。就是报错才正常,如果不报错那这行注释会提示错误。

第一个元素

14 - 第一个元素

by Anthony Fu (@antfu) #简单 #array

题目

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

例如:

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"7

知识点

  1. infer  在 extends 语句中,支持infer关键字,表示语句中待推断的类型变量。这个有点难以理解,还是看例子吧。

    interface Todo {
    title: string;
    description: string;
    completed: boolean;
    }
    type todoKey = keyof Todo // "title" | "description" | "completed"8

let func1: Func ; // => boolean let func2: Func<''>; // => boolean let func3: Func<() => Promise >; // => Promise

interface Todo {
title: string;
description: string;
completed: boolean;
}
type todoKey = keyof Todo // "title" | "description" | "completed"9

解答

  1. 利用 infer 获取第一个元素

    function getProperty(obj, key) {
    return obj[key];
    }0

  2. 利用 0 下标直接获取,判断一下是不是空数组。

    function getProperty(obj, key) {
    return obj[key];
    }1

  3. 利用 T['length'] 返回数组长度判断是否是空数组,然后用 0 下标获取第一个元素。

    function getProperty(obj, key) {
    return obj[key];
    }2

  4. 利用之前的知识点 T[number] 返回所有元素的联合类型。

    function getProperty(obj, key) {
    return obj[key];
    }3

这里需要注意下,First<[3, 2, 1]>,泛型里的所有参数都是类型而不是 JavaScript 对象。所以如果要从 JS 里引用的话,需要用 typeof。

获取元组长度

18 - 获取元组长度

by sinoon (@sinoon) #简单 #tuple

题目

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

例如:

function getProperty(obj, key) {
return obj[key];
}4

解答

这个没啥说的,知识点都是上面的。在泛型传入时约束下必须传入元组就行,否则 test case 过不了

function getProperty(obj, key) {
return obj[key];
}5

Exclude

43 - Exclude

by Zheeeng (@zheeeng) #简单 #built-in

题目

实现 TS 内置的 Exclude<T, K>,但不可以使用它。

function getProperty(obj, key) {
return obj[key];
}6

知识点

  1. extends
    extends 的另一个特性。分配式,当 extends 左边类型是一个联合类型时,会进行拆分,有点类似数学中的分解因式:

function getProperty(obj, key) {
return obj[key];
}7

解答

function getProperty(obj, key) {
return obj[key];
}8

Awaited

189 - Awaited

by Maciej Sikora (@maciejsikora) #简单 #promise #built-in

题目

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

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

这个挑战来自于 @maciejsikora 的文章:original article

解答

没啥说的,知识点都是上面的。传入值需要约束为 Promise,返回值需要递归判断 Promise 的泛型参数是否 Promise。

function getProperty(obj, key) {
return obj[key];
}9

If

268 - If

by Pavel Glushkov (@pashutk) #简单 #utils

题目

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

举例:

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}0

解答

这题本身很简单,注意约束一下 C 的传入类型就行。

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}1

关键在于 ts 配置,在不同的环境下,C extends boolean 有时 boolean 包含 null 有时不包含。

tsconfig.json 中开启 "strictNullChecks": true

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}2

建议在所有情况下都开启 null 的严格模式检查。

Concat

533 - Concat

by Andrey Krasovsky (@bre30kra69cs) #简单 #array

题目

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

举例,

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}3

解答

这个没啥说的,用 JS 的思路,使用展开运算符,然后看报错添加约束。

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}4

Includes

898 - Includes

by null (@kynefuk) #简单 #array

题目

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

举例来说,

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}5

解答

这道题在简单里有点复杂了。一般复杂的泛型最好先用 JS 来写一个思路,然后再转换成 TS 泛型。

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}6

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}7

或者自己写一个 Equal

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}8

Push

3057 - Push

by jiangshan (@jiangshanmeta) #简单 #array

题目

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

举例如下,

这时,我们直接添加泛型,会报错。因为现在无法确定 `K` 是否是 `T` 的成员属性。
```ts
function getProperty<T, K>(obj: T, key: K) {
  return obj[key];  // Error 类型“K”无法用于索引类型“T”。
}9

解答

又恢复到很简单的,没啥说的。

// 这里泛型的意思就是,K 是 T 成员属性中的一个。如果 K 传的值不是 T 成员属性其中之一,就会报错。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

// keyof T => "foo" | "bar" | "baz"

const foo = getProperty(obj, "foo"); // "foo" extends "foo" | "bar" | "baz",正常

const b = getProperty(obj, "b");  // "b" extends "foo" | "bar" | "baz",报错0

Unshift

3060 - Unshift

by jiangshan (@jiangshanmeta) #简单 #array

题目

实现类型版本的 Array.unshift

举例,

// 这里泛型的意思就是,K 是 T 成员属性中的一个。如果 K 传的值不是 T 成员属性其中之一,就会报错。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

// keyof T => "foo" | "bar" | "baz"

const foo = getProperty(obj, "foo"); // "foo" extends "foo" | "bar" | "baz",正常

const b = getProperty(obj, "b");  // "b" extends "foo" | "bar" | "baz",报错1

解答

送分题,同上 。

// 这里泛型的意思就是,K 是 T 成员属性中的一个。如果 K 传的值不是 T 成员属性其中之一,就会报错。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

// keyof T => "foo" | "bar" | "baz"

const foo = getProperty(obj, "foo"); // "foo" extends "foo" | "bar" | "baz",正常

const b = getProperty(obj, "b");  // "b" extends "foo" | "bar" | "baz",报错2

Parameters

3312 - Parameters

by midorizemi (@midorizemi) #简单 #infer #tuple #built-in

题目

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

解答

算是熟悉 infer 的适用吧。

// 这里泛型的意思就是,K 是 T 成员属性中的一个。如果 K 传的值不是 T 成员属性其中之一,就会报错。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

// keyof T => "foo" | "bar" | "baz"

const foo = getProperty(obj, "foo"); // "foo" extends "foo" | "bar" | "baz",正常

const b = getProperty(obj, "b");  // "b" extends "foo" | "bar" | "baz",报错3

以上就是本篇文章的全部内容啦,感谢观看。

原文:https://juejin.cn/post/7103046028235898888
打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/5806.html

相关推荐

typescript的书?

typescript的书?

   从一些简单的例子来学习类型体操,效率更高。    所有题目来自 type-challenges 的简单挑战。 ...

TypeScript 2023.03.29 0 20

支付宝
微信
赞助本站