Typescript 函数 / 声明文件

| 函数的类型

| 定义函数

用接口定义函数的形状

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

使用类型别名

type Add = (x: number, y: number) => number;
let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; // error

完整的函数类型


// function add(arg1: number, arg2: number): number {
//   return x + y;
// }

// const add = (arg1: number, arg2: number): number => {
//   return x + y;
// };

let mySum = function (x: number, y: number): number {
    return x + y;
};

上面的代码只对 = 右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果手动给 mySum 添加类型,则应该是这样:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
// 同价于
let mySum: (x: number, y: number) => number;
mySum = function (x: number, y: number): number {
    return x + y;
};

注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。

在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。 在 ES6 中,=> 叫做箭头函数。

| 参数

| 可选参数

前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用 ? 表示可选的参数:

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了。

| 参数默认值

在 ES6 中允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受「可选参数必须接在必需参数后面」的限制了。

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

| 剩余参数

const handleData = (arg1: number, ...args: number[]) => {};
handleData(1, "a"); // error 类型"string"的参数不能赋给类型"number"的参数

| 重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

比如实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 ‘hello’ 的时候,输出反转的字符串 ‘olleh’。

利用联合类型,可以这么实现:

function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

这时,我们可以使用重载定义多个 reverse 的函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

重载只能用 function 来定义,不能使用接口、类型别名等

| 声明文件

通常我们会把类型声明放到一个单独的文件中,这就是声明文件:

// jQuery.d.ts
declare var jQuery: (string) => any;

我们约定声明文件以 .d.ts 为后缀。

然后在使用到的文件的开头,用「三斜线指令」表示引用了声明文件:

/// <reference path="./jQuery.d.ts" />
jQuery('#foo');

第三方声明文件

社区已经有多种方式引入声明文件,不过 TypeScript 2.0 推荐使用 @types 来管理。

@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:

npm install @types/jquery --save-dev