TypeScript泛型详解

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

示例

我们先实现一个student方法获取不同属性来看看使用:

JavaScript实现

//定义学生的属性获取方法,一个返回字符串类型的name一个返回number类型的年龄
let itemFun = {
    getName: (name)=>{
        return name||"Tom";
    },
    getAge: (age)=>{
        return age||20;
    }
}
//定义student学生实例中间方法,直接返回处理后的数据
student = (value) => {
    return value;
}
//对于JavaScript来说并不关心返回类型,以上使用完全没有问题
let name = student(itemFun.getName('Jack'));
let age = student(itemFun.getAge(18));

TypeScript实现

//定义学生的属性获取方法,一个返回字符串类型的name一个返回number类型的年龄
let itemFun = {
    getName: (name: string): string=>{
        return name;
    },
    getAge: (age: number):number=>{
        return age;
    }
}

如果我们还是和以上方式一样去定义公共处理方式和调用就会出现以下问题:

student = (value) => {
    return value;
}
let name: string = this.student(itemFun.getName('Jack'));
let age: number = this.student(itemFun.getAge(18));

我们写方法是没有添加任何类型校验,那么TypeScript就通过类型推断把student定义成了如下形式:

student = (value: any):any => {
    return value;
}

这样就失去了本身TypeScript中类型校验的意义了,在开发中我们能不使用any就尽量不要使用。

那么我们如何定义才能实现既能返回string 又能返回 number

方案一、使用多个中间方法

student1 = (value: string): string => {
    return value;
}
student2 = (value: number): number => {
    return value;
}
let name: string = this.student1(itemFun.getName('Jack'));
let age: number = this.student2(itemFun.getAge(18));

这样最大的问题就是失去了本身封装的便捷性,有多个属性方法时需要多个中间处理,基本不可取

方案二、使用泛型

泛型就是指定一个表示类型的变量,用它来代替某个实际的类型用于编程,而后通过实际调用时传入或推导的类型来对其进行替换,以达到一段使用泛型程序可以实际适应不同类型的目的。

箭头函数

这样写的话tsx会直接编译报错
student2 = <T>(value: T): T => {
    return value;
}
//JSX element 'T' has no corresponding closing tag.ts(17008)
//Expression expected.

Intellisense gets very upset and complains, because it is trying to make the into a React JSX element. But my intention is to have the compiler treat it as a generic type designator.

Intellisense非常沮丧和抱怨,因为它试图将变成React JSX元素。 但我的目的是让编译器将其视为通用类型指示符。

那我们如果依然想使用箭头函数改如何处理呢?

When you have a single type parameter, TypeScript isn’t sure whether it might be a JSX opening tag or not. It has to choose one, so it goes with JSX.

当您有一个类型参数时,TypeScript不确定它是否可能是JSX开始标记。 它必须选择一个,所以它与JSX一起使用。

If you want a function with the exact same semantics, you can explicitly list the constraint of T:

如果你想要一个具有完全相同语义的函数,你可以明确列出T的约束:

student = <T extends {}>(value: T): T => {
    return value;
}
let name: string = this.student(itemFun.getName('Jack'));
let age: number = this.student(itemFun.getAge(18));
console.log(name, age);//Jack 18

This breaks the ambiguity for TypeScript so that you can use a generic type parameter. It also has the same semantics because type parameters always have an implicit constraint of {}.

这打破了TypeScript的歧义,以便您可以使用泛型类型参数。 它也具有相同的语义,因为类型参数始终具有{}的隐式约束。

普通函数

student<T>(value: T): T {
    return value;
}
let name: string = this.student(itemFun.getName('Jack'));
let age: number = this.student(itemFun.getAge(18));
console.log(name, age);//Jack 18

这样就可以正常使用泛型;

(注意:以上的方法本人都是在class类内部实现的,所以方法调用时都添加了this)

泛型解释

以上student后面紧接的 <T> 表示声明一个表示类型的变量,Value: T 表示声明参数是 T 类型的,后面的 : T 表示返回值也是 T 类型的。

在调用时:

let name: string = this.student(itemFun.getName('Jack'));
//(method) Init.student<string>(value: string): string

student(itemFun.getName('Jack'))由于参数推导出来的是string类型,所以这个时候T 代表了 string,因此此时 student 的返回类型也就是 string

let age: number = this.student(itemFun.getAge(18));
//(method) Init.student<number>(value: number): number

student(itemFun.getAge(18))由于参数推导出来的是number类型,所以这个时候T 代表了 number,因此此时student的返回类型也就是 number

在方法调用时也可以指定T代替的类型:

let name: string = this.student<string>(itemFun.getName('Jack'));
let age: number = this.student<number>(itemFun.getAge(18));

如果不指定T类型,TypeScript会推导出当前的T指代的类型;

注意:如果指定的类型和推导出的有冲突就会报错:

let age: number = this.student<string>(itemFun.getAge(18));
//报错如下
const itemFun: {
    getName: (name: string) => string;
    getAge: (age: number) => number;
}
Argument of type 'number' is not assignable to parameter of type 'string'.ts(2345)

泛型类

泛型类使用(<>)括起泛型类型,跟在类名后面。

普通泛型类

// 声明泛型类,类型变量为 T
class FilteredList<T> {
    // 声明过滤器是以 T 为参数类型,返回 boolean 的函数表达式
    filter: (v: T) => boolean;
    // 声明数据是 T 数组类型
    data: T[];
    constructor(filter: (v: T) => boolean) {
        this.filter = filter;
    }

    add(value: T) {
        if (this.filter(value)) {
            this.data.push(value);
        }
    }

    get all(): T[] {
        return this.data;
    }
}

// 处理 string 类型的 FilteredList
const validStrings = new FilteredList<string>(s => !s);

// 处理 number 类型的 FilteredList
const positiveNumber  = new FilteredList<number>(n => n > 0);

react中的泛型类

普通的react类

import PropTypes from 'prop-types';
export default class Animal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            aState: '',
    		bState: ''
        }
    }
}
App.propTypes = {
  aProps: PropTypes.string.isRequired,
  bProps: PropTypes.string.isRequired,
};

TypeScript

本文的 interface 定义默认以 I 开头

interface IProps {
  color?: string;
  size?: string;
}
interface IState {
  aState: string;
  bState: number;
}
export default class Animal extends React.Component<IProps, IState> {
    constructor(props:IProps) {
        super(props);
        this.state = {
            aState: '',
    		bState: 0
        }
    }
}

TypeScript 可以对 JSX 进行解析,充分利用其本身的静态检查功能,使用泛型进行 PropsState 的类型定义。定义后在使用 this.statethis.props 时可以在编辑器中获得更好的智能提示,并且会对类型进行检查。

在这里可以看到 Component 这个泛型类, P 代表 Props 的类型, S 代表 State 的类型。

class Component<P, S> {
    readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
    state: Readonly<S>;
}

Component 泛型类在接收到 PS 这两个泛型变量后,将只读属性 props 的类型声明为交叉类型 Readonly<{ children?: ReactNode }> & Readonly<P>; 使其支持 children 以及我们声明的 colorsize

通过泛型的类型别名 Readonlyprops 的所有属性都设置为只读属性。

Readonly 实现源码 node_modules/typescript/lib/lib.es5.d.ts

由于 props 属性被设置为只读,所以通过 this.props.size = 'sm' 进行更新时候 TS 检查器会进行错误提示,Error:(23, 16) TS2540: Cannot assign to 'size' because it is a constant or a read-only property

发布了58 篇原创文章 · 获赞 78 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/gongch0604/article/details/99576592