一个函数有输入和输出,要在 TypeScript
中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
function sum(x: number, y: number): number {
return x + y;
}
输入多余的(或者少于要求的)参数,都是不被允许的。
sum(1, 2, 3); // error 多余参数
sum(1); //Expected 2 少于要求
如果要我们现在写一个对函数表达式(Function Expression)
的定义,可能会写成这样:
const sum = (x: number, y: number): number => x + y;
这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行类型定义,而等号左边的 sum
,是通过赋值操作进行 类型推论 推断出来的。如果我们需要手动给 sum
添加类型,则应该是这样:
const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
不要混淆了
TypeScript
中的=>
和ES6
中的=>
。在
TypeScript
的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
我们可以通过接口来定义函数的类型:
interface ISum {
(x: number, y: number): number
}
const sum: ISum = (x, y) => x + y;
前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?
与接口中的可选属性类似,我们用 ?
表示可选的参数:
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat: string = buildName('Tom', 'Cat');//确定2个参数
let tom: string = buildName('Tom');//省略不确定参数(可选参数)
需要注意的是,可选参数必须接在确定参数后面。换句话说,可选参数后面不允许再出现确定参数。
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
// error 是按顺序写参数,可选参数后不可以再出现确定参数
在 ES6
中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数:
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
此时就不受「可选参数必须接在必需参数后面」的限制了:
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
ES6
中,可以使用 ...rest
的方式获取函数中的剩余参数(rest 参数)
:
function push(array: number[], ...items: number[]) {//定义第一个参数为number的数组,第二个参数使用...方法,把第一个参数外的参数聚到里面,形成数组
items.forEach(function (item) {
array.push(item);
})
}
var arr = [1, 2, 3]
push(arr,1,2,4,4,4,4,43,4)//arr后面无论传多少个参数,会形成一个数组,就是把剩余参数组成数组
事实上,items
是一个数组,所以我们可以用数组的类型来定义:
function push<A, B>(array: A[], ...items: B[]): void {
items.forEach(item => {
console.log(item);
})
}
重载允许一个函数接收不同数量或类型的参数时,作出不同的处理。
比如,我们需要实现一个函数 reverse
,输入数字 123
时,返回反转的数字 321
,输入字符串 hello
时,返回反转的字符串 olleh
,利用联合类型,我们可以这样实现:
type Reverse = string | number;
function reverse(x: Reverse): Reverse {
if (typeof x === "number") {
return Number(x.toString().split('').reverse().join(''));
} else {
return x.split('').reverse().join('');
}
}
const a =reverse("123")//提示reverse类型
type Reverse = string | number;
//重载:方法名一样,有多种实现
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: Reverse): Reverse {
if (typeof x === "number") {
return Number(x.toString().split('').reverse().join(''));
} else {
return x.split('').reverse().join('');
}
}
const a =reverse("123")//提示string类型
以上代码,我们重复多次定义了 reverse
函数,前几次都是函数的定义,最后一次是函数的实现,这时,在编译阶段的提示中,就可以正确的看到前两个提示了。
TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
然而这样做有一个缺点,就是不能 精确 的表达,输入数字的时候,返回也是数字,输入字符串的时候,也应该返回字符串。这时,我们可以使用重载定义多个reverse
函数类型:
在 typescript
中,我们可以使用 interface
(接口) 来定义复杂数据类型,用来描述形状或抽象行为。如:
interface IPerson {
name: string;
age: number;
sayName(): void;//定义函数没有参数没有返回值也可以写成这样 sayName: () => void
}
//上面定义了IPerson 对象,结构有name age sayName
const p: IPerson = {
name: "tom",
age: 21,
sayName() {
console.log(this.name);
}
};
//上面定义了p=IPerson类型,所以结构必须完全一致,类型匹配
变量 p
的类型是 IPerson
,这样就约束了它的数据结构必须和 IPerson
保持一致,多定义和少定义都是不被允许的。
赋值的时候,变量的形状必须和接口的形状保持一致。
有时,我们希望不要完全匹配接口中的属性,那么可以用可选属性:
interface IPerson {
name: string;
age: number;
gender?: string; // 可选属性打个问号就行
sayName(): void;
}
const p: IPerson = {
name: "tom",
age: 21,
sayName() {
console.log(this.name);
}
};
在进行赋值时, gender
属性是可以不存在的。当然,这时仍然不允许添加接口中未定义的属性。
有时候我们希望对象中的一些属性只能在创建的时候被赋值,那么可以用 readonly
定义只读属性:
interface IPerson {
readonly id: number; // 只读属性 后面就不可修改
name: string;
age: number;
gender?: string;
sayName(): void;
}
只读约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。 因此,在对象初始化的时候,必须赋值,之后,这个属性就不能再赋值。
const p: IPerson = {
id: 1,
name: "tom",
age: 21,
sayName() {
console.log(this.name);
}
};
const vs readonly:变量用 const,对象属性用 readonly
有时候,我们希望一个接口允许有任意属性:比如现在没办法定义,想要后期定义
interface IPerson {
readonly id: number;
name: string;
age: number;
gender?: string;
sayName(): void;
[propsName: string]: any; // 任意属性
}
IPerson.a=1;//不给任意属性情况下回报错
[propsName: string]: any;
通过 字符串索引签名 的方式,我们就可以给 IPerson
类型的变量上赋值任意数量的其他类型。
const p: IPerson = {
id: 1,
name: "tom",
age: 21,
email: "[email protected]", // 任意属性
phone: 1234567890, // 任意属性
sayName() {
console.log(this.name);
},
};
email
和 phone
属性没有在 IPerson
中显性定义,但是编译器不会报错,这是因为我们定义了字符串索引签名。
一旦定义字符串索引签名,那么接口中的确定属性和可选属性的类型必须是索引签名类型的子集。
type Fn= ()=>void//通过别名放进数据类型
interface IPerson {
name: string;
age?: number;
[propName: string]: string|Fn;//写了任意属性,必须把可选属性和只读属性等(你写到所有属性,包括函数)的数据类型写到里面
}
// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.ts(2411)
// (property) IPerson.age?: number | 报错,上面定义了number类型,没有吧number放进任意属性
[propName: string]: string;
字符串索引签名类型为 string
,但是可选属性 age
是 number
类型,number
并不是 string
的子集, 因此编译报错。