函数类型
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];
}
泛型种类
- 接口泛型
interface id<T, U> {
id1: T;
id2: U;
}
- 类泛型
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";