TypeScript系列之--有趣理解函数类型&泛型

函数类型

TS 定义函数类型需要定义输入参数类型和输出类型。

输出类型也可以忽略,因为 TS 能够根据返回语句自动推断出返回值类型。

function add(x:number, y:number):number {  
  return x + y
}
add(1,2)

函数没有明确返回值,默认返回 Void 类型

function welcome(): void {   
  console.log('hello')
}

函数表达式写法:

它写法有点类似于箭头函数

function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);

interface 描述函数类型

interface ISum {   
   (x:number,y:number):number
}
const add:ISum = (num1, num2) => {   
  return num1 + num2
}

type 描述函数

type addType = (num1:number,num2:number) => number

可选参数

参数后加个问号,代表这个参数是可选的

function add(x:number, y:number, z?:number):number {  
  return x + y
}
add(1,2,3)
add(1,2)

注意可选参数要放在函数入参的最后面,不然会导致编译错误

默认参数

跟 JS 的写法一样,在入参里定义初始值。

和可选参数不同的是,默认参数可以不放在函数入参的最后面。但如果带默认值的参数不是最后一个参数,用户必须明确的传入 undefined值来获得默认值。

function add(x:number = 100, y:number):number {   
  return x + y
} 
add(100)
//看上面的代码,add 函数只传了一个参数,
//如果理所当然地觉得 x 有默认值,只传一个就传的是 y 的话,就会报错,
//编译器会判定你只传了 x,没传 y

add(undefined,100) // OK

泛型

TS 文档中对泛型的介绍:
 

软件工程的一个重要部分就是构建组件,组件不仅需要有定义良好和一致的 API,也需要是可复用的(reusable)。好的组件不仅能够兼容今天的数据类型,也能适用于未来可能出现的数据类型,这在构建大型软件系统时会给你最大的灵活度。

在比如 C# 和 Java 语言中,用来创建可复用组件的工具,我们称之为泛型(generics)。利用泛型,我们可以创建一个支持众多类型的组件,这让用户可以使用自己的类型消费(consume)这些组件。

这***说的简直不是人话,简直就是听君一席话,胜似一席话。。。

泛型基本使用

泛型的语法是 <> 里写类型参数,一般可以用 T 来表示;

function print<T>(arg:T):T {   
  console.log(arg)  
  return arg
}

这里的T就好似一个变量,他是什么类型由用户使用的时候传入, 通过泛型我们就能做到了输入和输出的类型统一,且可以输入输出任何类型。

泛型就是对类型编程

本质上,泛型可以理解为一个类型层面的函数,当我们指定具体的输入类型时,得到的结果是经过处理后的输出 类型 ,平时我们都是对值进行编程,泛型是对类型进行编程。

举一个例子:假如我们定义了一个 Person 类型,这个 Person 类有三个属性,并且都是必填的 ,但现在我想把它三个属性变成选填的,我们不可能重新写一个(重新写not elegant), 那么对类型的操作就剩两种了 :一种是集合操作,另一种是今天的泛型

interface Persion {
  name: string;
  age: number;
  height: number; 
}

先看集合操作:

interface Persion {
  name: string;
  age: number;
  height: number;
}
type optionPersion = Persion & {
  name?: string;
}
const ikun:optionPersion = {
  age: 36,
  height: 36
}//报错 类型 "Persion" 中需要该name属性

interface Persion {
  name?: string;
} //报错  后续属性声明必须属于同一类型。属性“name”的类型必须为“string”

似乎集合操作做不到这一点呀 ! 假如我们可以像操作函数那样操作类型,是不是有可能呢?比如我定义了一个函数 Partial,这个函数的功能入参是一个类型,返回值是新的类型,这个类型里的属性全部变成可选的 ,如下:

function Partial(Type) {
    type objType =  空类型
    for(k in Type) {
        //将内部变为可选。。。。
      objType?[k] :objType[k]
    }
    return objType
}

type PartialedPerson = Partial(Person)

但是 上面代码随便写的,不能运行 。原因是JS无法对类型进行操作

那我们来看下泛型 Partial 的具体实现,可以看出其没有直接使用 JS 的语法,而是自己定义了一套语法

function Partial(Type) {type objType =  空类型for(k in Type) {
        //将内部变为可选。。。。
      objType?[k] :objType[k]
    }return objType}

type Partial<T> = { 
  [P in keyof T]?: T[P]
};

先不管别的,我们就看看他们有多像吧!

  • 从外表看只不过是 function 变成了 type,() 变成了 <>而已。
  • 从语法规则上来看, 函数内部对标的是 ES 标准。而泛型对应的是 TS 实现的一套标准

再来看个例子:

function ids<T, U>(ary1: T, arg2: U): [T, U] {
  return [arg1, arg2];
}

泛型种类

  1. 接口泛型
interface id<T, U> {
  id1: T;
  id2: U;
}
  1. 类泛型
class MyComponent extends React.Component<Props, State> {
   ...
}

泛型约束

假设现在有这么一个函数,打印传入参数的长度,我们这么写:

function printLength<T>(arg: T): T { 
    console.log(arg.length)  
    return arg
}

因为不确定 T 是否有 length 属性,会报错。可以和 interface 结合,使用extends关键字来约束泛型

  interface ILength {  
    length: number
 }
 function printLength<T extends ILength>(arg: T): T {  
    console.log(arg.length)  
     return arg
  }
const str = printLength('lin') // 我们定义的变量一定要有 length 属性,才可以通过 TS 编译

默认参数

可以像JS函数一样给泛型加一个默认值,如下

type A<T = string> = Array<T>;
const aa: A = [1]; // type 'number' is not assignable to type 'string'.
const bb: A = ["1"]; // ok
const cc: A<number> = [1]; // ok

什么时候用泛型

当你的函数,接口或者类:

  • 需要作用到很多类型的时候,比如上面print的泛型声明。
  • 需要被用到很多地方的时候,比如Partial 泛型。

泛型支持函数嵌套

type CutTail<Tuple extends any[]> = Reverse<CutHead<Reverse<Tuple>>>

如上代码 ,Reverse 是将参数列表反转,CutHead 是将数组第一项切掉。因此 CutTail 的意思就是将传递进来的参数列表反转,切掉第一个参数,然后反转回来。具体实现有点复杂,但知道泛型支持嵌套就够了。具体参考https://zhuanlan.zhihu.com/p/147248333

泛型支持递归

泛型甚至可以嵌套自己从而形成递归,比如单链表的定义就是递归的。

type ListNode<T> = {
  data: T;
  next: ListNode<T> | null;
};

再比如 HTMLElement 的定义。

declare var HTMLElement: {
    prototype: HTMLElement;
    new(): HTMLElement;
};

我们再来看一个更复杂一点的递归形式 - 递归调用,这个递归调用的功能是:递归地将类型中所有的属性都变成 可选。类似于深拷贝那样,只不过这不是拷贝操作,而是变成可选,并且是作用在类型,而不是值。

type DeepPartial<T> = T extends Function
  ? T
  : T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

type PartialedWindow = DeepPartial<Window>; // 现在window 上所有属性都变成了可选啦

常见TS 泛型工具及实现

Partial:

功能是将类型的属性变成可选。注意这是浅 Partial,DeepPartial 上面提过,只要配合递归调用使用即可。

type Partial<T> = { [P in keyof T]?: T[P] };

Required:

功能和Partial 相反,是将类型的属性变成必填, 这里的 -指的是去除。 -? 意思就是去除可选,也就是必填。。。。

type Required<T> = { [P in keyof T]-?: T[P] };

Mutable:

功能是将类型的属性变成可修改,这里的 -指的是去除。 -readonly 意思就是去除只读,也就是可修改。。。

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

Readonly:

功能和Mutable 相反,功能是将类型的属性变成只读, 在属性前面增加 readonly 意思会将其变成只读。

type Readonly<T> = { readonly [P in keyof T]: T[P] };

ReturnType:

功能是用来得到一个函数的返回值类型。

type ReturnType<T extends (...args: any[]) => any> = T extends (
  ...args: any[]
) => infer R
  ? R
  : any;

下面的示例用 ReturnType 获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了

type Func = (value: number) => string;

const foo: ReturnType<Func> = "1";

有帮助到你点个赞再走吧!

参考资料:

函数_TypeScript中文文档

https://zhuanlan.zhihu.com/p/64446259

TypeScript: Documentation - Utility Types

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值