《四》大话 Typescript泛型

2,616 阅读5分钟

前言: 本文章为 TypeScript 系列文章. 旨在利用碎片时间快速入门 Typescript. 或重新温故 Typescript 查漏补缺.在官方 api 的基础上, 加上一些日常使用的感想. 如果感兴趣的话~ 欢迎关注, 后续持续推出文章.

文章列表:

目录结构:

  • 为何要用泛型
  • 泛型约束
  • 泛型语法
  • 实际应用
  • 高级类型: 索引类型
  • react 实际场景

为何要用泛型

javascript 作为一门动态语言, 在实际运行的时候,等变量被赋值才知道该变量的类型. 动态语言给实际的编码带来了很大的灵活性.

function getVal(val) {
    retrurn val;
}
getVal(1) // 返回数字类型
getVal('1') // 返回字符串类型
getVal(['2']) // 返回数组类型

但是同样的, 在代码运行期间有可能会发生与类型相关的错误, 降低了代码的可维护性. 那下面我们用 typescript 来定义变量. 为了支持3种调用方式, 我们需要声明三种类型定义. 有2种方法可以解决上面这个问题.

  1. 函数重载
function getVal(val: number): number 
function getVal(val: string):string 
function getVal(val: any):any {
    return val;
}
  1. 联合类型
function getVal(val: string | number | any[]):string | number | any[] {
    return val;
}

作为一个程序员, 上面2种方法让我们感受重复繁琐. 这是无法容忍的. 那怎么样可以让该函数又可以在运行的时候被赋值才确定该变量的类型, 又有一定的类型约束减少相关的错误? 答案就是: 泛型

function getVal<T>(val: T): T {
    return val;
}

T 即代表捕获函数传入的参数类型, 然后在函数内部使用 T 即用该参数类型声明其他变量. 但是我们从上面的函数看出, 因为 T 是捕获参数传入的参数类型,

这个函数可以传入任意参数, 与我们最开始只支持3个类型的需求不符. 所以下面要引入泛型约束.

泛型约束

type Params=  string | number | any[];
function getVal<T extends Params>(val: T): T {
    return val;
}
getVal(1);
getVal('2');
getVal(['222']);
getVal<number>('3'); // 跟泛型指定的类型不一致, 报错
getVal({}); // 不是 Param 类型, 报错

泛型语法

泛型即可以声明函数, 也可以声明类. 也可以声明接口

class Person<T>{} // 一个尖括号跟在类名后面
function Person<T> {}  // 一个尖括号跟在函数名后面
interface Person<T> {}  // 一个尖括号跟在接口名后面

有些时候, 一个类或者一个函数里面, 他不止要用到一个动态类型, 他要用到多个. 但是我们上面只能捕获一个, 那直接声明多个不就好了?

function getName<T,U> (name: T, id: U): [T, U] {
    return [name, id]
}
getName('peen', 1);
getName('peen', '222'); // 正常
getName<string, number>('peen', '22'); // 报错: '22'不是number类型

实际应用

在实际项目中, 每个项目都需要接口请求, 我们会封装一个通用的接口请求, 在这个函数里面, 处理一些常见的错误等等. 为了让每个接口调用都有 typescript 约束, 提醒. 这里使用泛型是非常合适了.

interface IApiSourceParams {
    GetList: IGetList
}
interface IGetList {
    id: number;
}
export function fetchApi<T extends keyof IApiSourceParams>(action: T, params: IApiSourceParams[T]) {
    return ajax({
        url: action,
        method: 'POST',
        body: params
    })
}
fetchApi('GetList', { id: 2 });
fetchApi('GetList', { id: '33' }); // 报错, id 应该是 number 类型

这样子, 我们就给一个通用的接口请求函数增加了类型约束. 在 IApiSourceParams 中扩展每一个接口类型即可.

从上面的例子看到了 T extends keyof IApiSourceParams , 这在上文中没有看到. 实际这种应用场景特别多.

高级类型: 索引类型

索引类型查询操作符: keyof , 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合. 看着话有点绕, 直接看例子吧

interface Person {
    name: string;
    age: number;
}
let personProps: keyof Person; // 'name' | 'age'

索引访问操作符 : T[K] . 上面的 keyof 实际就是获取了对象的键值, 看一下上面的实际例子

interface IApiSourceParams {
    GetList: IGetList,
    PostApi: IPostApi
}
interface IGetList {
    id: number;
}
export function fetchApi<T extends keyof IApiSourceParams>(action: T, params: IApiSourceParams[T]) {
    return ajax({
        url: action,
        method: 'POST',
        body: params
    })
}
// IApiSourceParams[T] 获取的便是接口 IApiSourceParams 对应key值的接口 IGetList. 

react 实际场景

泛型在各大库中都非常广泛的使用, 下面选react中的类和react hook 实地分析

一、react 类

如果使用过 react , 肯定见过下面这种语法声明 props 和 state

class Test extends Component<IProps, IState> {}

我们再看看 react class 里面的 typescript 声明

class Component<P, S> {
        static contextType?: Context<any>;
        context: any;
        constructor(props: Readonly<P>);
        constructor(props: P, context?: any);
        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;
        forceUpdate(callBack?: () => void): void;
        render(): ReactNode;
        readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>;
        state: Readonly<S>;
        refs: {
            [key: string]: ReactInstance
        };
    }

可以看到react以及提前帮忙做好了一些约束

  1. constructor 的 props 属性都是只读属性
  2. setState 只能是 K extends keyof , 声明的 state 里面的类型.
  3. ……

二、React HOOK

我们随便找一个最常使用的 useState 方法.

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

所以我们在使用的时候, 可以

const [errorMessage, setError] = useState<string>('');

总结

本篇文章讲了泛型解决的场景以及语法. 并介绍了一些实际的应用场景. 最后欢迎关注「前端加加」,认真学前端,做个有专业的技术人...