TypeScript 中泛型的理解

泛型的定义

泛型程序设计(generic programming)是程序设计语言的一种风格或范式

泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在typescript中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性

假设我们用一个函数,它可接受一个 number 参数并返回一个number 参数,如下写法:

function returnItem (para: number): number {
    return para
}

如果我们打算接受一个 string 类型,然后再返回 string类型,则如下写法:

function returnItem (para: string): string {
    return para
}

上述两种编写方式,存在一个最明显的问题在于,代码重复度比较高

虽然可以使用 any类型去替代,但这也并不是很好的方案,因为我们的目的是接收什么类型的参数返回什么类型的参数,即在运行时传入参数我们才能确定类型

这种情况就可以使用泛型,如下所示:

function returnItem<T>(para: T): T {
    return para
}

可以看到,泛型给予开发者创造灵活、可重用代码的能力

使用方式

泛型通过< >的形式进行表述,可以声明:

函数

接口

函数声明

function returnItem<T>(para: T): T {
    return para
}

定义泛型的时候,可以一次定义「多个类型参数」,比如我们可以同时定义泛型 T 和 泛型 U:

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

接口声明

声明接口的形式如下:

interface ReturnItemFn<T> {
    (para: T): T
}

那么当我们想传入一个number作为参数的时候,就可以这样声明函数:

const returnItem: ReturnItemFn<number> = para => para

类声明

使用泛型声明类的时候,既可以作用于类本身,也可以作用于类的成员函数

下面简单实现一个元素同类型的栈结构,如下所示:

class Stack<T> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

使用方式如下:

const stack = new Stacn<number>()

如果上述只能传递 string 和 number 类型,这时候就可以使用 的方式猜实现「约束泛型」,如下所示:

type Params = string | number

class Stack<T extends Params> {
	private arr:T[]=[]
	public push(item:T){
	  this.arr.push(item)
	}
    public pop(){
    	this.arr.pop()
	}
}
const stack = new Stack<boolean>() //类型boolean 不满足约束 Params

除了上述的形式,泛型更高级的使用如下:

例如要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值

这时候就设计到泛型的索引类型和约束类型共同实现

索引类型、约束类型

索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示:

function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key] // ok
}

上述为什么需要使用泛型约束,而不是直接定义第一个参数为 object类型,是因为默认情况 object 指的是{},而我们接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如 T extends object

使用如下图所示:

TypeScript 中泛型的理解_第1张图片

多类型约束

例如如下需要实现两个接口的类型约束:

interface FirstInterface {
  doSomething(): number
}

interface SecondInterface {
  doSomethingElse(): string
}

可以创建一个接口继承上述两个接口,如下:

interface ChildInterface extends FirstInterface, SecondInterface {

}

正确使用如下:

class Demo<T extends ChildInterface> {
  private genericProperty: T

  constructor(genericProperty: T) {
    this.genericProperty = genericProperty
  }
  useT() {
    this.genericProperty.doSomething()
    this.genericProperty.doSomethingElse()
  }
}

通过泛型约束就可以达到多类型约束的目的

三、应用场景

通过上面初步的了解,后述在编写 typescript 的时候,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性的时候,这种情况下就可以使用泛型

灵活的使用泛型定义类型,是掌握typescript 必经之路

<泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量
 /*------------基础使用方法------------*/
  function join<T, P>(first: T, second: P): T {
    return first;
  }
  //const twoParms = join(1, '我是string');
  const twoParms = join(1, '我是string');

  /*---------泛型集合--------------*/
  function map<T>(params: Array<T>) {
    return params;
  }
  //const sanleType = map(['123']);
  const sanleType = map(['123']);

  /* -----------泛型箭头函数-------------*/
  const identity = <T,>(arg: T): T => {
    return arg;
  };
  const identity2: <T>(arg: T) => T = (arg) => {
    return arg;
  };

泛型接口

 /* -------------泛型接口-------------*/
 interface ColumnProps<T> {
  key: number | string;
  title: string;
  dataIndex: keyof T; // 约束 dataIndex 值需为引用泛型 T 中的属性
}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
const columns: Array<ColumnProps<ITableItem>> = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
  ];

泛型类

 /*--------------泛型类---------------*/
  class Person<T> {
    love: T;
    say: (arg: T) => T;
  }
  let myFn: IGeneric<number> = fn;
  myFn(13); //13
 
  let me = new Person<string>();
  me.love = 'TS';
  // me.love = 520; // ERROR
  me.say = function(love: string){
    return `my love is ${love}`;
  }

泛型约束
泛型可以通过 extends 一个接口来实现泛型约束,写法如:

<泛型变量 extends 接口>
<T, K extends keyof T>
//K为传入的T上已知的属性,

interface IArray {
  length: number
}
function logIndex<T extends IArray>(arg: T): void {
  for (let i = 0; i < arg.length; ++i) {
    console.log(i)
  }
}
let arr = [1, 2, 3]
// logIndex(arr) // 报错
logIndex<number[]>(arr) // 允许
logIndex(arr) // 自动类型推导,允许

泛型应用场景之一

/*-------------应用场景start---------------------------*/
interface ColumnProps<T> {
  key: number | string;
  title: string;
  dataIndex: keyof T; // 约束 dataIndex 值需为引用泛型 T 中的属性
}
interface ITableItem {
  key: number | string;
  name: string;
  address: string;
  age: number;
}
interface TableProps {
  dataSource: ITableItem[];
  columns: Array<ColumnProps<ITableItem>>;
}
const MyTable = (props: TableProps) => {
  const { dataSource, columns } = props;
  return <Table dataSource={dataSource} columns={columns} />;
};
const ApplcationMod = () => {
  const dataSource = [
    {
      key: '1',
      name: '金城武',
      age: 32,
      address: '西湖区湖底公园1号',
    },
    {
      key: '2',
      name: '吴彦祖',
      age: 42,
      address: '西湖区湖底公园1号',
    },
  ];

  const columns: Array<ColumnProps<ITableItem>> = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age',
    },
    {
      title: '住址',
      dataIndex: 'address',
      key: 'address',
    },
  ];

  return (
    <div>
      <h3>泛型应用场景</h3>
      <MyTable dataSource={dataSource} columns={columns} />
    </div>
  );
};

keyof 关键字
keyof 关键字非常实用,后面可以看到很多工具泛型都是使用 keyof 实现的。
keyof T(索引类型查询)的结果为 T上已知的公共属性名的联合。
keyof 的一个特性: keyof T的类型会被认为是 string、number、symbol 的子类型。

interface Point {
    x: number;
    y: number;
}

type Axis = keyof Point;
// 等同于 type Axis = "x" | "y"
function cal(a: Point, b: Point, axis: Axis): number {
  return (a[axis] + b[axis]) / 2;
}
cal({x:20,y:99},{x:100,y:200},'x')

type类型别名
1.可以创建联合类型、元组类型、基本类型(string,number,symbol)

  //联合类型
   type Pets = 'hi' | 'age' | 'hello';
  interface Dog {
    x:string
  }
  interface Cat {
    y:number
  }
  type Pet = Dog | Cat
  //元组+泛型
  //与 声明数组类型 类似,只不过在 数组 基础上更加细分化每个元素的类型。
  type Pair<T> = [T, T]; 
  const ff: Pair<number> = [1, 2];
  const cc: Pair<number> = [1, 1];
  const dd: Pair<string> = ['1', '2'];

2.可以利用type和映射类型快速创建类型

 { [ K in P ] : T }

实例

type Coord = {
    [K in 'x' | 'y']: number;
};
// 得到
// type Coord = {
//  x: number;
//  y: number;
// }
它执行了一个循环(可以理解为类似 for...in 的效果),这里的 P 直接设置为 'x' | 'y' 的一个联合类型,而 K 是一个标识符,它映射为 P 的每一个子类型。T 为数据类型,我们直接固定为 number,也可以是任何其他复杂类型。

//约束key,value为同样的值;
type Coord = { [K in 'x' | 'y']: K };
// 得到 type Coord = { x: 'x'; y: 'y'; }

3:生成类型的函数类型
可以封装一个快速生成接口类型的函数类型:

  type Unite = 'dog' | 'cat' | 'pig';
  type PetInfo = {
    name: string;
    age: number;
  };
  type Coord4 = {
    [K in Unite]: PetInfo;
  };
  const animalInfo: Coord4 = {
    dog: { name: 'dogname', age: 3 },
    cat: { name: 'catname', age: 3 },
    pig: { name: 'pigname', age: 3 },
  };
//等同于
  type Creat<K extends keyof any, U> = {
    [P in keyof K]: U;
  };
//预置的高级类型 Record ,可以直接使用
  type Pets = Creat<Unite,PetInfo>
  const animalInfo2: Pets = {
    dog: { name: 'dogname2', age: 3 },
    cat: { name: 'catname2', age: 3 },
    pig: { name: 'pigname2', age: 3 },
  };

interface 与 type
相同点

/*都可以描述一个对象或者函数*/
interface User {
  name: string
  age: number
}
interface SetUser {
  (name: string, age: number): void;
}
type User = {
  name: string
  age: number
};

type SetUser = (name: string, age: number)=> void;
*都允许拓展(extends*/
interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
type Name = { 
  name: string; 
}
type User = Name & { age: number  };
//interface extends type
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
//type extends interface
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

不同点
type 可以声明基本类型别名,联合类型,元组等类型
nterface 能够声明合并

interface User {
  name: string
  age: number
}

interface User {
  sex: string
}
/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。

TypeScript 的映射类型
简单语法

{ [ K in P ] : T }
type Coord = {
    [K in 'x' | 'y']: T;
};
// 得到
// type Coord = {
//  x: T;
//  y: T;
// }
首选确定它执行了一个循环(可以理解为类似 for...in 的效果),这里的 P 直接设置为 'x' | 'y' 的一个联合类型,而 K 是一个标识符,它映射为 P 的每一个子类型。T 为数据类型,我们直接固定为 number,也可以是任何其他复杂类型。
因为 T 值数据类型可以是任何值,甚至数值 1 也可以,因此我们把数据类设成 K 自身也行:

type Coord = { [K in 'x' | 'y']: K };
// 得到 
type Coord = { x: 'x'; y: 'y'; }

立用映射类型+keyOf关键字 封装一个快速生成接口类型的函数类型Record

//封装一个快速生成接口类型的函数类型
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
//K extends keyof any
等价于
//type K = 'dog' | 'cat' | 'fish';

type PetType = 'dog' | 'cat' | 'fish';

interface PetInfo {
  name: string;
  age: number;
}

type Pets = Record<PetType, PetInfo>;

const pets: Pets = {
  dog: { name: 'didi', age: 1 },
  cat: { name: 'cici', age: 2 },
  fish: { name: 'fifi', age: 3 }
};
or
interface Pets {
  dog: PetInfo;
  cat: PetInfo;
  fish: PetInfo;
}

额外的属性检查
需求:代码迁移复用,希望原先的 js 代码能够迁移到 ts 中,增加类型推断
1.使用 as 关键字,断言类型,
as 语法,大部分其他场景应该避免使用,类型断言纯粹是编译时语法。类型推断应该尽量使用 interface / type / 基础类型

//const asFoo = {};//报错写法
//const asFoo = {} as Foo;
const asFoo: { [propName: string]: any } = {};//推荐字符串索引用法
asFoo.bar = 2;//error类型“{}”上不存在属性“bar”。
asFoo.title = 'as 类型断言';//error类型“{}”上不存在属性“bas”。

interface Foo {
  bar: number;
  title: string;
  [propName: string]: any;//可解决报错
}

类型注解&类型推段

TS能够自动的分析变量类型时,就什么都不需要做了
TS无法分析出变量类型时,就需要使用类型注解。
定义基础类型可不用写类型注解
TypeScript 中泛型的理解_第2张图片

//let count:number = 1;
let count = 1;//不需要写类型注解;
const fistCount = 1;
const secondCount = 2;
const total = fistCount + secondCount;//不需要加类型注解,ts可直接推断出total为number类型

//返回值类型不写也不会报错,但是实战中一般还是要做一层约束。
function getTotal(fistCount:number,secondCount:number):number{
  return fistCount + secondCount;
}
const total = getTotal(1,2);//不需要加类型注解,ts分析出是number类型

你可能感兴趣的:(typescript,c#,javascript)