typescript的泛型(Generics)

1,589 阅读4分钟

背景:公司项目使用的组件库是ant-design,代码要求用typescript,刚开始写代码,总是红色波浪线😭, 后面索性用any代替一切类型,在使用过程中编辑器还是提示一些错误😭,比如使用ant-design的Form组件要使用props.form时必须这样写Form.create<AddProps>()(Add),看到<>这是啥???好像大学时学c++时见过(只是见过),后面看了几遍文档终于弄明白了,当自己开始真正实操写代码时,又熟悉的红色波浪线😭又出现了,决定写一篇文章结合实际项目的运用,让自己入个门...

官方解释泛型

A major part of software engineering is building components that not only have well-defined and consistent APIs, but are also reusable. Components that are capable of working on the data of today as well as the data of tomorrow will give you the most flexible capabilities for building up large software systems.In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types. 传送门 (翻译: 为了偷懒及编辑器友好的代码提示)

快速上手

为了学习(不报错)泛型,我准备了一个例子,便于快速入门, 我的解说全部放代码注释了,直接看代码。

// 学习部分
/*****************************
* 泛型: (通用的类型)
* 泛:(广泛:很多,普遍)
* 型: (类型: 比如number, string, object, array)
* 下面的T即是泛型(通用类型), T可以随便命名的, 我当初看的时候,以为只能<T>, <V>这样写。
***************************/
function identity<T>(arg: T): T {
    return arg;
}
// 调用时把T换成真正的类型
identity<string>("myString")

实际运用

代码我简化过哦

函数中运用

举的栗子是父组件调子组件内部方法

/***************************************
* 场景:React在父组件里面,
* 想调用子组件里面的helloWorld函数
*****************************************/
import React from 'react'
import Child from './child'
function Parent(){
    // 不使用泛型这样写
    const childRef = React.createRef();
    // 使用泛型这样写
     const childRef = React.createRef<Child>();
    return (
    <div>
     <button onClick={() => {
        /*****************************
        *使用泛型的好处体现下面这一行代码, 当你敲 childRef!.current!.
        * 编辑器会提示组件Child里面的方法, 比如helloWord这个方法。
        ********************************/
         childRef!.current!.helloWorld()
     }>调用子组件</button>
     <Child ref={childRef}/>
    </div>
    )
}

组件中运用

示例一个泛型组件,并如何在tsx里面使用

 // child.tsx 文件
 import React, { Component } from 'react';
 
 /**********************************************
 * interface是一个接口, PropsType接受泛型T
 *******************************************/
 interface PropsType<T>{
    sayWhat?: string,
    input: T, // input的类型是泛型T
   // callback是一个函数,接受2个参数,
    callback(input: T, e: React.MouseEvent<HTMLElement>): void 
 }
/**************************************************************
* childTemplate为泛型, 
* PropsType<childTemplate>, 接口PropsType的泛型T被赋值为childTemplate泛型
************************************************/
class Child<childTemplate> extends Component<PropsType<childTemplate>>{
      render() {
        const { props } = this;
        return (<button onClick={(e) => {
            console.log(props.sayWhat || '没传sayWhat')
            // props.input的类型为childTemplate
            props.callback(props.input, e)
        }}>子组件</button>)  
      }
}

// parent.tsx 文件

 import React, { Component } from 'react';
 import Child from './child'
 // 定义接口CTemplate
 interface CTemplate{
    a: string,
    b: number
 }
class Parent extends Component{
      render() {
       // 定义inputChild
        const inputChild: CTemplate = {
            a: 'hello world',
            b: 77
        }
        return (
            // 划重点,在tsx里面的使用,把泛型实例化,这样写<Child<CTemplate> />
            <Child<CTemplate> input={inputChild} callback={
                (input: CTemplate, e: React.MouseEvent<HTMLElement>) => {
                    console.log('----------in parent component-------------')
                    // input里面定义了a和b, 这个时候可以享受编辑器的自动提示了
                    console.log(input.a + input.b)
                    console.log(e)
                }
            }/>
        )  
      }
}

ant-design使用的一些tips

准备我们经常用的From表单讲解一下泛型,加深自我的理解。

在使用ant-design的组件库里我们经常会用到Form.create这个函数,我们通过查阅文档来了解它的使用,其实我们也可以通过它的定义文件来了解它的使用, 光标定位到From.create, 按快捷键(vscode和webstorm快捷键 ctr+B)进入定义文件

// antd/lib/form/Form.d.ts
static create: <TOwnProps extends FormComponentProps<any>>(options?:
FormCreateOption<TOwnProps>) => FormWrappedProps<TOwnProps>;

通过上面一行代码,我们可以获取的信息有:

  1. create函数接受泛型 TOwnProps ,TOwnProps继承FormComponentProps, 因此我们在接口中定义Props接受的参数时要继承FormComponentProps,继续把光标定位到FormComponentProps,按ctr+B进入定义的地方

从上图可以知道,在props里面会有form这个对象。

  1. 接收参数类型为: FormCreateOption, 当我们想知道怎么传时,继续按ctr+B定位到定义的地方,

  1. 函数返回FormWrappedProps,继续按ctr+B定位到定义的地方,我们可以知道FormWrappedProps是一个函数,(component:C) => ConnectedComponentClass, 参数接受组件,返回组件。
export declare type FormWrappedProps<TOwnProps extends WrappedFormInternalProps> = 
<C extends React.ComponentType<Matching<TOwnProps, GetProps<C>>>>(component: C) => 
ConnectedComponentClass<C, Omit<TOwnProps, keyof WrappedFormInternalProps>>;

看的我头痛....通过上面的一系列ctrl+b,我们知道了Form.create的调用方式了,

// YourPropsInterface, FormOption, YourComponent
import React, { useState, Fragment } from 'react';
import { FormComponentProps } from 'antd/es/form';
interface AddProps extends FormComponentProps {
}
function Add(props: AddProps) {
    return <div></div>
}
Form.create<AddProps>({name: 'add-component'})(Add)

总结

缺点:typescript的泛型(Generics)写起来麻烦,看起来难看

优点:编辑器代码提示,代码更规范,其他小伙伴更容易维护你的代码😰😰😰