非常教程

Typescript参考手册

声明文件 | Declaration Files

Deep Dive

定义文件理论:深度潜水

构建模块以提供所需的确切API形状可能会非常棘手。例如,我们可能想要一个可以被调用的模块new来产生不同的类型,在层次结构中有多种命名类型,并且在模块对象上也有一些属性。

通过阅读本指南,您将拥有编写复杂定义文件的工具,这些文件将提供友好的API表面。本指南关注模块(或UMD)库,因为这里的选项更多。

关键概念

通过理解TypeScript的一些关键概念,您可以充分理解如何进行任何形式的定义。

类型

如果您正在阅读本指南,您可能已经大致了解TypeScript中的类型。然而,更明确的是,一种类型被引入:

  • 一个类型别名声明(type sn = number | string;
  • 一个接口声明(interface I { x: number[]; }
  • 类声明(class C { }
  • 一个枚举声明(enum E { A, B, C }
  • 一种import引用某种类型的声明

这些声明表单中的每一个都创建一个新的类型名称

与类型一样,您可能已经理解了什么是价值。值是我们可以在表达式中引用的运行时名称。例如let x = 5;创建一个名为的值x

再次明确地说,以下事情创造价值:

  • letconstvar声明
  • namespacemodule包含值的声明
  • 一个enum声明
  • 一个class声明
  • 一个import引用一个值的声明
  • 一个function声明

命名空间

类型可以存在于名称空间中。例如,如果我们有声明let x: A.B.C,我们说这个类型C来自A.B命名空间。

这种区别是微妙而重要的 - 在这里,A.B不一定是一种类型或价值。

简单的组合:一个名字,多重含义

给定一个名称A,我们可能会找到三种不同的含义A:类型,值或名称空间。名称的解释方式取决于其使用的上下文。例如,在声明中let m: A.A = A;A首先用作名称空间,然后用作类型名称,然后用作值。这些含义最终可能指的是完全不同的声明!

这看起来可能会让人困惑,但只要我们不会过分重载,它实际上非常方便。我们来看看这种组合行为的一些有用的方面。

内置组合

例如,精明的读者会注意到,类型价值清单class都出现了这种情况。声明class C { } 创建两件事情:一个类型C,其指的是类的实例的形状,并且一个C,其指的是类的构造函数。枚举声明的行为类似。

用户组合

假设我们写了一个模块文件foo.d.ts

export var SomeVar: { a: SomeType };
export interface SomeType {
  count: number;
}

然后消耗它:

import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);

这很好,但我们可以想象SomeType并且SomeVar密切相关,因此您希望它们具有相同的名称。我们可以使用组合来以相同的名称呈现这两个不同的对象(值和类型)Bar

export var Bar: { a: Bar };
export interface Bar {
  count: number;
}

这为消费代码中的解构提供了一个非常好的机会:

import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);

再一次,我们在Bar这里既是类型又是值。请注意,我们不必将该Bar值声明为该Bar类型的值- 它们是独立的。

高级组合

某些类型的声明可以在多个声明中组合使用。例如,class C { }interface C { }可共存的,都有助于性能的C类型。

只要不产生冲突,这是合法的。一般的经验法则是,值总是与其他相同名称的值相冲突,除非它们被声明为namespaces,如果类型别名声明(type s = string)被声明,那么类型将会发生冲突,并且名称空间永远不会冲突。

我们来看看如何使用。

添加 interface使用。

我们可以interface通过另一个interface声明添加更多的成员:

interface Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

这也适用于类:

class Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

请注意,我们不能添加以使用接口来键入aliases(type s = string;)。

添加使用 namespace

一个namespace声明可以被用来在不产生冲突的任何方式增加新的类型,值和命名空间。

例如,我们可以为一个类添加一个静态成员:

class C {
}
// ... elsewhere ...
namespace C {
  export let x: number;
}
let y = C.x; // OK

请注意,在这个例子中,我们向(它的构造函数)的静态端添加了一个值C。这是因为我们添加了一个,并且所有值的容器都是另一个值(类型由名称空间包含,名称空间由其他名称空间包含)。

我们也可以为类添加一个名称空间类型:

class C {
}
// ... elsewhere ...
namespace C {
  export interface D { }
}
let y: C.D; // OK

在这个例子中,C直到我们namespace为它写了声明之前,没有一个名称空间。含义C为命名空间不符合的价值或意义类型冲突C由类创建的。

最后,我们可以使用namespace声明执行许多不同的合并。这不是一个特别实际的例子,但显示了各种有趣的行为:

namespace X {
  export interface Y { }
  export class Z { }
}

// ... elsewhere ...
namespace X {
  export var Y: number;
  export namespace Z {
  export class C { }
  }
}
type X = string;

在本例中,第一个块创建以下名称含义:

  • X(因为namespace声明包含一个值Z
  • 一个名称空间X(因为该namespace声明包含一个类型Y
  • A型YX命名空间
  • A型ZX命名空间(的类的实例形状)
  • 作为值Z的属性的X值(该类的构造函数)

第二个块创建以下名称含义:

  • Y(类型的number),其是所述的属性X
  • 一个命名空间 Z
  • 作为值Z的属性的X
  • A型CX.Z命名空间
  • 作为值C的属性的X.Z
  • 一种 X

使用export =import

一个重要的原则是,exportimport声明导出或导入全部含义的目标。

Typescript

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。

主页 https://www.typescriptlang.org
源码 https://github.com/Microsoft/TypeScript
发布版本 2.6.0