TypeScript:得泛型者,得天下

简介: TypeScript:得泛型者,得天下

这篇文章跟大家分享学习ts的又一个重难点「泛型」。在ts中,得泛型者,得天下!


1


什么是泛型


整个ts的学习,其实就是各种数据类型的类型约束的学习。当我们规定一个变量只能是number时,它就不能是其他数据类型。

let a: number = 20;
a = 'string'// 类型错误

在函数中也是一样,传入的参数类型,与返回的参数类型,都会被不同的约束规则约束。

function foo(a: number, b: string): string {
  // todo
}

当然我们也知道,还可以使用interface,type定义更为复杂的类型约束。可是这个时候我们就会面临一个问题。


以我们用的非常多的数组方法map为例。

[1, 2, 3].map(item => {
  return item + 1;
})

我们都知道map方法接收的第一个参数为一个回调函数callback,callback的第一个参数为数组的每一项。那么问题就来了,不同的数组调用map,数组的每一项数据类型必然不一样,我们没办法简单的使用某一种数据类型来准确的约束数组的每一项。

[1, 2, 3].map()
['a', 'b', 'c'].map()

怎么办?当数组不一样时?如何来约束呢?


这种情况,需要借助「泛型」来帮助我们。


单一的,明确的类型约束理解起来相对简单,可是实践中我们需要对约束稍微放宽一点限制,那么单一的约束就无法满足需求。泛型,即为更广泛的约束类型。


仔细观察下面的三组案例,思考一下如果我们要自己描述Array类型与数组中的map方法应该怎么做?

interface Person {
  name: string,
  age: number
}
const demo1: number[] = [1, 2, 3];
const demo2: string[] = ['a', 'b', 'c'];
const demo3: Person[] = [{ name: 'alex', age: 20 }, { name: 'john', age: 10 }, { name: 'hx', age: 21 }];
demo1.map((item) => item);
demo2.map((item) => item);
demo3.map((item) => item);

注意观察不同数组item的不同类型


从图中可以看出,当不同的数组调用map时,回调函数的参数item,会自动推导为对应的数据类型。也就是说,这里的item,必然是使用了泛型进行了更为宽松的约束。具体如下:

interface Array<T> {
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[]
}

我们在声明数组类型时,定义了一个泛型变量T。T作为泛型变量的含义为:我们在定义约束条件时,暂时还不知道数组的每一项数据类型到底是什么,因此我们只能放一个占位标识在这里,待具体使用时再来明确每一项的具体类型。


因此针对数据的描述,我们通常可以这样做:

const arr1: Array<number> = [1, 2, 3];
const arr2: Array<string> = ['a', 'b', 'c'];
const arr3: Array<Person> = [{ name: 'alex', age: 20 }, { name: 'john', age: 10 }, { name: 'hx', age: 21 }];

这里分别定义了三个数组,在约束这些数组时,我们明确了泛型变量T的具体数据类型,分别对应为number, string, Person。


那么在描述map时的写法就很好理解了。回调函数callbackfn的第一个参数就是数组的每一项,正好就是定义数组时传入的泛型变量T,不过回调函数会返回一个新的数组项,因此我们需要重新定义一个新的泛型变量来表达这个新数组,即为U。

map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[]

于是我们就使用泛型,准确的描述了map方法的含义。


如果经过上述的解释还不清楚泛型概念的话,留言


2


基础语法


如果完整的理解了泛型的概念,那么泛型的基础知识就比较简单了,过一遍就OK。


「函数中使用泛型」


// 声明一个泛型变量
function identity<T> {}
// 在参数中使用泛型变量
function identity<T>(arg: T) {}
// 在返回值中使用泛型变量
function identity<T>(arg: T): T {}
// 变量声明函数的写法
let myIdentity: <T>(arg: T) => T = identity;


「接口中使用泛型」


// 使用接口约束一部分数据类型,使用泛型变量让剩余部分变得灵活
interface Parseer<T> {
  success: boolean,
  result: T,
  code: number,
  desc: string
}
// 接口泛型与函数泛型结合
interface Array<T> {
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[]
}


「class中使用泛型」


// 注意总结相似性
declarenamespace demo02 {
  class GenericNumber<T> {
    private value: T;
    public add: (x: T, y: T) => T
  }
}
// 多个泛型变量传入
declarenamespace demo02 {
  class Component<P, S> {
    privateconstructor(props: P);
    public state: S;
  }
}


3


泛型实践场景


「描述数组」


interface Array<T> {
  length: number,
  toString(): string,
  pop(): T | undefined,
  // 注意此处的含义
  push(...items: T[]): number,
  concat(...items: T[]): T[],
  join(separator?: string): string,
  reverse(): T[],
  shift(): T | undefined;
  slice(start?: number, end?: number): T[],
  sort(compareFn?: (a: T, b: T) =>number): this,
  splice(start: number, deleteCount?: number): T[],
  // 注意此处的重载写法
  splice(start: number, deleteCount: number, ...items: T[]): T[],
  unshift(...items: T[]): number,
  indexOf(searchElement: T, fromIndex?: number): number,
  lastIndexOf(searchElement: T, fromIndex?: number): number,
  every(callbackfn: (value: T, index: number, array: T[]) =>boolean, thisArg?: any): boolean,
  some(callbackfn: (value: T, index: number, array: T[]) =>boolean, thisArg?: any): boolean,
  forEach(callbackfn: (value: T, index: number, array: T[]) =>void, thisArg?: any): void,
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[],
  filter<S extends T>(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[],
  filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[],
  reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T,
  reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T,
  reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U,
  // reduceRight 略
  // 索引调用
  [n: number]: T,
}

列举了几乎所有的数组方法与特性,如果能够从上诉描述文件中掌握如何使用数组方法,那么就表示对于函数,接口,泛型的理解已经比较到位了。如果还不能读懂,则多读几遍,留言


「描述数据返回结果」


约定所有的接口返回满足统一的数据格式。但是具体的可用的数据结果则因为情况不同,会有不同的场景。因此使用泛型先定义一个基本的结构约束。

interface Result<T> {
  success: true,
  code: number,
  descript: string,
  result: T
}

结合Promise,当数据返回结果为number时


Promise本身就需要接受一个泛型变量,因此这里要注意泛型的嵌套使用

function fetchData(): Promise<Result<number>> {
  return http.get('/api/demo/number');
}

当数据返回结果为普通JSON数据时

interface Person {
  name: string,
  age: number
}
function fetchData(): Promise<Result<Person>> {
  return http.get('/api/demo/person');
}

当数据返回为数组时

interface Person {
  name: string,
  age: number
}
function fetchData(): Promise<Result<Person[]>> {
  return http.get('/api/demo/persons');
}

当返回结果为分页对象时

interface Person {
  name: string,
  age: number
}
interface Page<T> {
  current: number,
  pageSize: number,
  total: number,
  data: T[]
}
function fetchData(): Promise<Result<Page<Person>>> {
  return http.get('/api/demo/page/person');
}

分页对象的返回结果比较复杂,因此描述清楚需要多层嵌套,如果你理解了分页对象,那么基本上泛型就没有什么问题啦!

相关文章
|
9天前
|
JavaScript 前端开发 程序员
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
11 0
|
13天前
|
JavaScript 安全
TypeScript(十一)泛型工具类型
TypeScript(十一)泛型工具类型
15 0
|
13天前
|
JavaScript 前端开发 索引
TypeScript(十)泛型进阶
TypeScript(十)泛型进阶
12 0
|
13天前
|
JavaScript 开发者
TypeScript(九)泛型基础
TypeScript(九)泛型基础
10 0
|
1月前
|
JavaScript
TypeScript 泛型类型
TypeScript 泛型类型
|
1月前
|
JavaScript Java 编译器
TypeScript 泛型
TypeScript 泛型
|
1月前
|
JavaScript 算法 开发者
16.【TypeScript 教程】TypeScript 泛型(Generic)
16.【TypeScript 教程】TypeScript 泛型(Generic)
22 2
|
28天前
|
JavaScript 安全 编译器
TypeScript 基础学习笔记:泛型 <T> vs 断言 as
TypeScript 基础学习笔记:泛型 <T> vs 断言 as
26 0
|
1月前
|
JavaScript 编译器
typescript 泛型约束
typescript 泛型约束
|
2月前
|
JavaScript 安全 前端开发
【TypeScript技术专栏】TypeScript泛型编程技巧
【4月更文挑战第30天】TypeScript的泛型编程允许编写适应多种数据类型的函数和类,提高代码复用性。基本概念包括在声明函数、接口或类时使用类型参数。示例:泛型函数`identity&lt;T&gt;`、泛型接口`GenericIdentityFn&lt;T&gt;`和泛型类`GenericNumber&lt;T&gt;`。技巧包括类型推断、泛型约束和泛型数组。在实际开发中,泛型用于创建通用库、装饰器和中间件,提升代码灵活性和复用性。