d.ts

栏目: jQuery · 发布时间: 7年前

内容简介:经常看到We call declarations that don’t define an implementation “ambient”. Typically these are defined in .d.ts files. If you’re familiar withC/C++, you can think of these as.h files.

一.简介

经常看到 d.ts ,因为一个越来越广泛的应用场景是编辑器智能提示(具体见 IntelliSense based on TypeScript Declaration Files ):

JavaScript IntelliSense can be provided for values declared in a .d.ts file (more info), and types such as interfaces and classes declared in TypeScript are available for use as types in JsDoc comments.

d.ts 大名叫TypeScript Declaration File,存放一些声明,类似于C/C++的 .h 头文件( #include <stdio.h> ):

We call declarations that don’t define an implementation “ambient”. Typically these are defined in .d.ts files. If you’re familiar withC/C++, you can think of these as.h files.

(摘自 Working with Other JavaScript Libraries )

P.S.另一个“ambient”(环境?)相关的概念 Ambient Namespace ,指的也是只有声明没有实现的namespace

二.分类

声明文件本身没有类别,但不同类型的类库在API暴露方式等方面存在差异,对应的声明文件也有所区别

例如jQuery 1.x通常以global库的方式引用:

<script type="text/javascript" src="script/jquery.min.js"></script>
<script>
  $( "button.continue" ).html( "Next Step..." )
</script>

而jQuery 3.x支持模块引用:

// ES Module
import $ from "jquery";
// Commonjs Module
const $ = require("jquery");

从声明文件上看,前者需要声明全局变量 jQuery 和 $ ,而后者并不默认暴露这些,所以 jQuery-1.x.d.ts :

declare var jQuery: JQueryStatic;
declare var $: JQueryStatic;

jQuery-3.x.d.ts :

export = jQuery;

因此,我们把类库分为3类:

  • global:暴露出全局变量的类库

  • module:不暴露全局变量,需要通过特定加载机制(如 require/define/import )引用的模块形式的类库

  • plugin:会影响其它类库功能的类库(当然,也可能会影响原声明,比如添个新API)

3种类库对应的声明文件细分成6种,模板及适用场景如下:

  • global.d.ts :适用于global类库

  • module-function.d.ts :适用于暴露出一个Function的module类库

  • module-class.d.ts :适用于暴露出一个Class的module类库

  • module.d.ts :适用于一般module类库(暴露出的东西既不是Function也不是Class)

  • module-plugin.d.ts :适用于module plugin类库(A module plugin changes the shape of another module.)

  • global-plugin.d.ts :适用于global plugin类库(A global plugin is global code that changes the shape of some global.)

  • global-modifying-module.d.ts :适用于module形式的global plugin类库(They’re similar to global plugins, but need a require call to activate their effects.)

P.S.另外,声明文件也存在全局声明冲突的问题,建议 通过namespace解决

三.引用方式

不同类型的声明文件对应的引用方式也不同,global类库声明通过 /// <reference types="..." /> 指令引用,例如:

/// <reference types="someLib" />

function getThing(): someLib.thing;

module类库声明用 import 语句引用:

import * as moment from "moment";

function getThing(): moment;

特殊的,UMD类库既可以作为module类库也可以作为global类库引入 ,此时引用方式取决于当前环境。例如global类库依赖UMD类库的话,仍通过 reference 指令来引用:

/// <reference types="moment" />

function getThing(): moment;

而module/UMD类库依赖UMD类库时则用 import 语句:

import * as someLib from 'someLib';

P.S.关于声明文件引用方式的更多信息,请查看

四.语法格式

全局变量

/** The number of widgets present */
declare var foo: number;

declare var 声明了一个数值类型的全局变量 foo ,同样,还有 declare const 和 declare let 分别表示只读,及块级作用域

类型部分与TS基本类型语法一致,具体见 Basic Types

全局函数

declare function greet(greeting: string): void;

declare function 声明了一个函数 greet ,它接受1个字符串类型参数 greeting ,返回 undefined 或 null

全局对象

declare namespace myLib {
    function makeGreeting(s: string): string;
    let numberOfGreetings: number;
}

declare namespace 声明了一个对象 myLib ,身上有个方法 makeGreeting 接受1个字符串类型参数 s ,返回字符串,还有个数值类型的属性 numberOfGreetings

函数重载

declare function getWidget(n: number): Widget;
declare function getWidget(s: string): Widget[];

getWidget 函数有两个签名,其一接受1个数值类型参数 n ,返回Widget,其二接受1个字符串类型参数 s ,返回Widget数组

接口

interface GreetingSettings {
  greeting: string;
  duration?: number;
  color?: string;
}

declare function greet(setting: GreetingSettings): void;

interface 是一种可复用的类型,上例声明了 GreetingSettings 的结构,要求参数 setting 具有 greeting 以及可选的 duration 和 color 属性,类型分别为字符串、数值、字符串

类型别名

type GreetingLike = string | (() => string) | MyGreeter;

declare function greet(g: GreetingLike): void;

type 也是一种可复用类型,上例声明了类型别名 GreetingLike ,要求参数 g 是字符串或返回字符串的函数或 MyGreeter 实例

类型“模块”

declare namespace GreetingLib {
    interface LogOptions {
        verbose?: boolean;
    }
    interface AlertOptions {
        modal: boolean;
        title?: string;
        color?: string;
    }
}

类似于 namespace 能够组织代码模块(把一组相关代码放在一起), declare namespace 能用来组织类型“模块”(把一组相关类型声明放在一起)

P.S.同样,嵌套namespace也是支持的,如 declare namespace GreetingLib.Options {/*...*/}

类

declare class Greeter {
    constructor(greeting: string);

    greeting: string;
    showGreeting(): void;
}

declare class 声明了一个类 Greeter ,拥有构造函数和成员变量 greeting 以及成员方法 showGreeting

五.实践规范

除了遵循基本的语法格式外,实践中还应该遵守这些规范约束:

  • 用基础类型( number, string, boolean, object ),不要用包装类型( Number, String, Boolean, Object )

  • 不要出现未使用的泛型参数,会导致 类型无法正确推断

  • 无返回值的callback参数返回类型用 void ,不要用 any

  • callback的可选参数没必要在类型上标出来,因为callback允许少传/不传参数

  • 函数重载需要注意声明顺序,应该从特殊到一般自上而下排列(例如 any 会短路其它重载声明,类似于模式匹配的机制)

  • 能用可选参数(如 two?: string )描述的就别用函数重载了

  • 能用组合类型(如 b: number|string )描述的就别用函数重载了

六.类型,值和命名空间

实际上,类型,值和命名空间,这3个基本概念构成了TS灵活多样的类型系统

具体的,类型有5种声明方式:

// 类型别名
type sn = number | string;
// 接口
interface I { x: number[]; }
// 类
class C { }
// 枚举
enum E { A, B, C }
// 类型引用
import * as m from 'someModule';

值有6种声明方式:

// 变量
let, const, var
// 模块
namespace, module
// 枚举
enum
// 类
class
// 值引用
import
// 函数
function

命名空间可以用来组织类型,例如 let x: A.B.C 表示变量 x 的类型是来自 A.B 命名空间下的 C

发现 class 、 enum 、 import 具有 双重含义 ,没错,它们既声明值也提供类型,于是出现了一些有意思的事情:

// 值与类型的结合
export var Bar: { a: Bar };
export interface Bar {
  count: number;
}

// 类型与类型的结合
interface Foo {
  x: number;
}
class Foo {
  y: number;
}
// ... elsewhere ...
interface Foo {
  z: number;
}
let a: Foo = ...;
console.log(a.x + a.y + a.z); // OK

// 命名空间与类型的结合
class C {
}
// ... elsewhere ...
namespace C {
  export let x: number;
}
let y = C.x; // OK

// 命名空间与命名空间的结合
namespace X {
  export interface Y { }
  export class Z { }
}

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

基本原则是, 只要不冲突就是合法的 ,具体而言,相同命名空间下的同名值存在冲突,同名类型别名存在冲突,而命名空间不会和其它东西冲突:

Values always conflict with other values of the same name unless they are declared as namespaces, types will conflict if they are declared with a type alias declaration (type s = string), and namespaces never conflict.

所以上例中的某些命名( Bar 、 Foo )虽然存在多种含义,但都不冲突,仍然是合法的

七.自动生成

dts-gen(不建议用)

# 全局安装dts-gen
npm install -g dts-gen

Microsoft/dts-gen 是官方脚手架工具(已经1年不更新了,但聊胜于无),能为JS生成 d.ts :

dts-gen is a tool that generates TypeScript definition files (.d.ts) from any JavaScript object.

能够帮助生成基本的API列表,例如:

export function containsEmoji(...args: any[]): any;
export function isEmoji(...args: any[]): any;

export namespace containsEmoji {
    const prototype: {
    };
}
export namespace isEmoji {
    const prototype: {
    };
}

具体用法如下:

// 从npm模块生成
dts-gen -m emoutils
// 从本地文件生成
dts-gen -e "require('/absolute-path-to/emoutils.js')"

P.S. require 本地文件要写绝对路径,否则无法正确加载,具体见 expression-file doesn’t seem to work; ‘Couldn’t load module “undefined”‘

用法有些奇怪,因为这个东西是运行时的:

It simply examines the objects as they appear at runtime, rather than needing the source code that creates the object.

所以,得到的API列表肯定全,但参数类型、JSDoc等就无能为力了,算是一种 取舍 :

This means no matter how the object was written, anything, including native objects, can be given an inferred shape.

代价是生成产物缺少很多源码相关的静态信息:

Keep in mind dts-gen doesn’t figure out everything – for example, it typically substitutes parameter and return values as any, and can’t figure out which parameters are optional.

dts-gen 生成的东西太弱了,那么,有没有更厉害的方式?

有。TypeScript编译源码时本来就会推断校验参数类型,函数签名等,这些信息输出出来就是 d.ts :

When a TypeScript script gets compiled there is an option to generate a declaration file (with the extension .d.ts) that functions as an interface to the components in the compiled JavaScript.

(摘自 Declaration files )

tsc(推荐)

安装:

# 全局安装typescript
npm install typescript -g
# 测试安装是否成功
tsc --version

使用:

# 后缀名改为.ts
cp ./path-to/my-file.js ./my-file.ts
# 从.ts生成d.ts
tsc --declaration my-file.ts

仅支持TS文件 , --allowJs 选项在这里不可用(更多相关信息见 Allow --declaration with --allowJs ):

error TS5053: Option ‘allowJs’ cannot be specified with option ‘declaration’.

静态语义分析比运行时强大很多,能够推断参数类型、识别JSDoc,生成结果如下:

/**
* 是不是一个emoji
* @param {String} str
*/
declare function isEmoji(str?: string): boolean;
/**
* 是否含有emoji
* @param {String}} str
*/
declare function containsEmoji(str?: string): boolean;
declare var emoutils: {
    isEmoji: (str?: string) => boolean;
    containsEmoji: (str?: string) => boolean;
    str2unicodeArray: (str?: string) => string[];
    length: (str?: string) => number;
    substr: (str?: string, start?: number, len?: number) => string;
    toArray: (str?: string) => any[];
};
export default emoutils;
export { isEmoji, containsEmoji, str2unicodeArray, length, substr, toArray };

相当完美 ,所以推荐使用这种方式,唯一缺点是要改后缀名,可以参考 How do I rename the extension for a batch of files?

八.发布

经常看到类似 @types/xxx 的npm模块,其实它们都来自 DefinitelyTyped/DefinitelyTyped

当然,也可以把自己模块的API声明放上去,具体见 How can I contribute? 以及 Microsoft/types-publisher 工具

除了发布独立的typings模块,还可以随功能模块一起发,有两种方式:

  • index.d.ts :把 index.d.ts 放在模块根目录下发布出去

  • 指定 types/typings :在 package.json 里添上 types (或者 typings )字段,例如 "types": "./lib/main.d.ts"

但 types/typings 都是非 npm标准字段 ,所以 建议使用第一种方式

安装

如果依赖的功能模块没附带types,可以通过 TypeSearch 搜索想要的typings模块,再单独安装,例如:

npm install --save @types/lodash

像功能模块一样正常引用即可:

import * as _ from "lodash";
_.padStart("Hello TypeScript!", 20, " ");

参考资料


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Flow-Based Programming (2nd Edition)

Flow-Based Programming (2nd Edition)

CreateSpace / 2010-5-14 / $69.95

Written by a pioneer in the field, this is a thorough guide to the cost- and time-saving advantages of Flow-Based Programming. It explains the theoretical underpinnings and application of this program......一起来看看 《Flow-Based Programming (2nd Edition)》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换