类型声明

类型声明

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之后,typeinterface声明的类型都能够得到正确的错误提示,也能够应用于大部分的继承、合并的场景。

// 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.jsprocess对象,则可以进行如下的显式声明:

// 声明全局变量
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

毫无疑问,DefinitelyTypedTypeScript最大的优势之一,社区已经记录了90%的顶级JavaScript库。这意味着,你可以非常高效地使用这些库,而无需在单独的窗口打开相应文档(以确保输入的正确性

可以通过npm来安装使用@types,如下例所示,你可以为jquery添加声明文件:

$ npm install @types/jquery --save-dev

@types支持全局和模块类型定义。默认情况下,TypeScript会自动包含支持全局使用的任何定义。例如,对于jquery,你应该能够在项目中开始全局使用 $。我们也可以像使用模块一样使用它:

import * as $ from "jquery";

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