类型声明
类型声明
type
type
关键字能够用于为基础类型(primitive type),联合类型(union type),以及交叉类型(intersection)取类型别名;TypeScript 还支持利用 typeof
关键字取变量类型,并且赋值给类型变量:
let Some = Math.round(Math.random()) ? "" : 1;
type numOrStr = typeof Some;
let foo: numOrStr;
foo = 123;
foo = "abc";
foo = {}; // Error!
值得一提的是,自 2.9 版本开始,typeof
关键字支持动态 import
的类型推导:
const zipUtil: typeof import("./utils/create-zip-file") = await import(
"./utils/create-zip-file"
);
interface
interface 关键字同样能够用于类型声明,用于定义对象的行为与约束;TypeScript 遵循所谓的 Structural Typing,即类型的适配与一致性依赖于实际的结构:
type RestrictedStyleAttribute = "color" | "background-color" | "font-weight";
interface Foo {
// 必要属性
required: Type;
// 可选属性
optional?: Type;
// Hash map,匹配任意字符串类型的键
[key: string]: Type;
// 转化为序列类型
[id: number]: Type;
// 匹配某些固定的键名
[T in RestrictedStyleAttribute]: string;
}
譬如简单的接口定义如下:
interface Story {
title: string;
description?: string;
tags: string[];
}
然后,任意定义包含 title
与 tags
属性的对象都会被当做 Story 接口的实例:
let story1: Story = {
title: "Learning TypeScript",
tags: ["typescript", "learning"],
};
接口中同样可以定义函数:
interface StoryExtractor {
extract(url: string): Story;
}
let extractor: StoryExtractor = { extract: (url) => story1 };
或者简写为:
interface StoryExtractor {
(url: string): Story;
}
let extractor: StoryExtractor = (url) => story1;
对于接口的使用,我们将会在下文进行详细的讨论。早期版本中,interface 声明的类型能够用于扩展或者继承的场景,并且能够进行声明合并,而 type 声明的类型就无此等特性。不过自从 TypeScript 2.1 之后,type 与 interface 声明的类型都能够得到正确的错误提示,也能够应用于大部分的继承、合并的场景。
// TS Error:
// Interface:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointInterface'. Property 'y' is missing in type '{ x: number; }'.
// Type alias:
Argument of type '{ x: number; }' is not assignable to parameter of type 'PointType'. Property 'y' is missing in type '{ x: number; }'.
我们可以使用重复定义某个接口,其声明会自动合并;而我们无法使用 type 来重复声明相同的类型变量:
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
const box: Box = { height: 5, width: 6, scale: 10 };
类与对象
有时候我们希望将某个类作为参数传递,这个时候的就需要将类声明如下:
type Constructor<T> = new (...args: any[]) => T;
type Constructable = Constructor<{}>;
type Constructor<T = {}> = new (...args: any[]) => T;
lib.d.ts
当你安装 TypeScript 时,会顺带安装 lib.d.ts 等声明文件。此文件包含了 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。
- 它自动包含在 TypeScript 项目的编译上下文中;
- 它能让你快速开始书写经过类型检查的 JavaScript 代码。
你可以通过指定 –noLib 的编译器命令行标志(或者在 tsconfig.json 中指定选项 noLib: true)从上下文中排除此文件。
"compilerOptions": {
"lib": ["dom", "es6"]
}
lib 分类如下:
-
JavaScript 功能:es5,es6,es2015,es7,es2016,es2017,esnext
-
运行环境:dom,dom.iterable,webworker,scripthost
-
ESNext 功能选项:es2015.core,es2015.collection,es2015.generator,es2015.iterable,es2015.promise,es2015.proxy,es2015.reflect,es2015.symbol,es2015.symbol.wellknown,es2016.array.include,es2017.object,es2017.sharedmemory,esnext.asynciterable
修改原始类型
当我们希望使用那些标准的 JavaScript 代码库时,我们同样需要了解该库提供 API 的参数类型;这些类型往往定义在 .d.ts
声明文件中。早期的类型声明文件都需要手动地编写与导入,而 DefinitelyTyped 是目前最大的开源类型声明库,其会自动抓取库的类型声明文件,保障我们更加顺滑地使用 TypeScript。如果我们需要在代码中使用第三方库或者全局提供的变量,则可以使用 declare 关键字声明,譬如我们要使用 Node.js 中 process 对象,则可以进行如下的显式声明:
// 声明全局变量
declare const require: (moduleId: string) => any;
declare const process: any;
// 声明/扩展命名空间下变量
declare namespace NodeJS {
interface ReadableStream {
destroy: () => {};
}
}
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function;
}
interface Math {
seedrandom(seed?: string): void;
}
// Math.seedrandom();
}
declare module "egg" {
// 声明了由插件注入的依赖
interface Application {
knex: Knex;
}
}
如果是某个未包含类型声明的 NPM 库,则可以使用 declare 声明其命名空间,譬如 antd/typings 中对于 rc 项目的引用:
declare module "rc-queue-anim";
而当我们发布自己的项目时,如果在 tsconfig.json 中设置了 "declaration": true
,那么执行 tsc 命令时会为每个 ts 文件生成对应的 d.ts 声明文件;当我们将项目发布时,可以在 package.json 中,可以通过 typings 属性指定需要暴露的类型声明文件入口;譬如 redux 的类型声明在 index.d.ts 中:
{
"typings": "./index.d.ts"
}
而后在 index.d.ts 文件中,导出内部类型,或者带类型描述的函数:
// 类型
export type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S;
// 函数
export function combineReducers<S>(reducers: ReducersMapObject<S, any>): Reducer<S>;
export function combineReducers<S, A extends Action = AnyAction>(reducers: ReducersMapObject<S, A>): Reducer<S, A>;
.d.ts
文件同样可以相互引用:
/// <reference path="custom-typings.d.ts" />
DefinitelyTyped
毫无疑问,DefinitelyTyped 是 TypeScript 最大的优势之一,社区已经记录了 90% 的顶级 JavaScript 库。这意味着,你可以非常高效地使用这些库,而无需在单独的窗口打开相应文档(以确保输入的正确性)。
可以通过 npm 来安装使用 @types,如下例所示,你可以为 jquery 添加声明文件:
$ npm install @types/jquery --save-dev
@types 支持全局和模块类型定义。默认情况下,TypeScript 会自动包含支持全局使用的任何定义。例如,对于 jquery,你应该能够在项目中开始全局使用 $
。我们也可以像使用模块一样使用它:
import * as $ from "jquery";
// 现在你可以此模块中任意使用$了 :)