类型声明

类型声明

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[];
}

然后,任意定义包含 titletags 属性的对象都会被当做 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";

// 现在你可以此模块中任意使用$了 :)
上一页
下一页