JavaScript 和 TypeScript 交叉口 —— 类型定义文件(*.d.ts)
在 《从 JavaScript 到 TypeScript 系列》 文章我们已经学习了 TypeScript 相关的知识。
TypeScript 的核心在于静态类型,我们在编写 TS 的时候会定义很多的类型,但是主流的库都是 JavaScript 编写的,并不支持类型系统。那么如何让这些第三方库也可以进行类型推导呢?
这篇文章我们来讲解 JavaScript 和 TypeScript 的静态类型交叉口 —— 类型定义文件。
这篇文章首发于我的个人博客 《听说》。
前端开发 QQ 群:377786580
类型定义文件
在 TypeScript 中,我们可以很简单的,在代码编写中定义类型:
interface IBaseModel {
say(keys: string[] | null): object
}
class User implements IBaseModel {
name: string
constructor (name: string) {
this.name = name
}
}
但是主流的库都是 JavaScript 编写的,TypeScript 身为 JavaScript 的超集,自然需要考虑到如何让 JS 库也能定义静态类型。
TypeScript 经过了一系列的摸索,先后提出了 tsd(已废弃)、typings(已废弃),最终在 TypeScript 2.0 的时候重新整理了类型定义,提出了 DefinitelyTyped。
DefinitelyTyped 就是让你把 "类型定义文件(*.d.ts)",发布到 npm
中,配合编辑器(或插件),就能够检测到 JS 库中的静态类型。
类型定义文件的以 .d.ts
结尾,里面主要用来定义类型。
例如这是 jQuery 的类型定义文件 中一段代码(为了方便理解做了一些改动)
// 定义 jQuery 需要用到的类型命名空间
declare namespace JQuery {
// 定义基本使用的类型
type Selector = string;
type TypeOrArray<T> = T | T[];
type htmlString = string;
}
// 定义 jQuery 接口,jquery 是一个 包含 Element 的集合
interface JQuery<TElement extends Node = HTMLElement> extends Iterable<TElement> {
length: number;
eq(index: number): this;
// 重载
add(selector: JQuery.Selector, context: Element): this;
add(selector: JQuery.Selector | JQuery.TypeOrArray<Element> | JQuery.htmlString | JQuery): this;
children(selector?: JQuery.Selector): this;
css(propertyName: string): string;
html(): string;
}
// 对模块 jquery 输出接口
declare module 'jquery' {
// module 中要使用 export = 而不是 export default
export = jQuery;
}
类型定义
*.d.ts
编写起来非常简单,经过 TypeScript 良好的静态类型系统洗礼过后,语法学习成本非常低。
我们可以使用 type
用来定义类型变量:
// 基本类型
type UserName = string
// 类型赋值
type WebSite = string
type Tsaid = WebSite
可以看到 type
其实可以定义各种格式的类型,也可以和其他类型进行组合。
// 对象
type User = {
name: string;
age: number;
website: WebSite;
}
// 方法
type say = (age: number) => string
// 类
class TaSaid {
website: string;
say: (age: number) => string;
}
当然,我们也可以使用 interface
定义我们的复杂类型,在 TS 中我们也可以直接定义 interface
:
interface Application {
init(): void
get(key: string): object
}
interface
和 type
(或者说 class
) 很像。
但是 type
的含义是定义自定义类型,当 TS 提供给你的基础类型都不满足的时候,可以使用 type
自由组合出你的新类型,而 interface
应该是对外输出的接口。
type
不可以被继承,但 interface
可以:
interface BaseApplication {
appId: number
}
export interface Application extends BaseApplication {
init(): void
get(key: string): object
}
declare
declare
可以创建 *.d.ts
文件中的变量,declare
只能作用域最外层:
declare var foo: number;
declare function greet(greeting: string): void;
declare namespace tasaid {
// 这里不能 declare
interface blog {
website: 'http://tasaid.com'
}
}
基本上顶层的定义都需要使用 declare
, class
也是:
declare class User {
name: string
}
namespace
为防止类型重复,使用 namespace
用于划分区域块,分离重复的类型,顶层的 namespace
需要 declare
输出到外部环境,子命名空间不需要 declare
。
// 命名空间
declare namespace Models {
type A = number
// 子命名空间
namespace Config {
type A = object
type B = string
}
}
type C = Models.Config.A
组合定义
上面我们只演示了一些简单的类型组合,生产环境中会包含许多复杂的类型定义,这时候我们就需要各种组合出强大的类型定义:
动态属性
有些类型的属性名是动态而未知的,例如:
{
'10086': {
name: '中国移动',
website: 'http://www.10086.cn',
},
'10010': {
name: '中国联通',
website: 'http://www.10010.com',
},
'10000': {
name: '中国电信',
website: 'http://www.189.cn'
}
}
我们可以使用动态属性名来定义类型:
interface ChinaMobile {
name: string;
website: string;
}
interface ChinaMobileList {
// 动态属性
[phone: string]: ChinaMobile
}
类型遍历
当你已知某个类型范围的时候,可以使用 in
和 keyof
来遍历类型,例如上面的 ChinaMobile 例子,我们可以使用 in
来约束属性名必须为三家运营商之一:
type ChinaMobilePhones = '10086' | '10010' | '10000'
interface ChinaMobile {
name: string;
website: string;
}
// 只能 type 使用, interface 无法使用
type ChinaMobileList = {
// 遍历属性
[phone in ChinaMobilePhones]: ChinaMobile
}
我们也可以用 keyof
来约定方法的参数
export type keys = {
name: string;
appId: number;
config: object;
}
class Application {
// 参数和值约束范围
set<T extends keyof keys>(key: T, val: keys[T])
get<T extends keyof keys>(key: T): keys[T]
}
集成发布
有两种主要方式用来发布类型定义文件到 npm
:
- 与你的 npm 包捆绑在一起(内置类型定义文件)
- 发布到 npm 上的 @types organization
前者,安装完了包之后会自动检测并识别类型定义文件。
后者,则需要通过 npm i @types/xxxx
安装,这就是我们前面所说的 DefinitelyTyped ,用于扩展 JS 库的类型声明。
内置类型定义文件
内置类型定义就是把你的类型定义文件和 npm 包一起发布,一般来说,类型定义文件都放在包根目录的 types
目录里,例如 vue:
如果你的包有一个主 .js
文件,需要在 package.json
里指定主类型定义文件。
设置 types
或 typeings
属性指向捆绑在一起的类型定义文件。 例如包目录如下:
├── lib
│ ├── main.js
│ └── main.d.ts # 类型定义文件
└── package.json
// pageage.json
{
"name": "demo",
"author": "demo project",
"version": "1.0.0",
"main": "./lib/main.js",
// 定义主类型定义文件
"types": "./lib/main.d.ts"
}
如果主类型定义文件名是 index.d.ts
并且位置在包的根目录里,就不需要使用 types
属性指定了。
├── lib
│ └── main.js
├── index.d.ts # 类型定义文件
└── package.json
如果你发的包中,package.json
中使用了 files
字段的话(npm
会根据 files
配置的规则决定发布哪些文件),则需要手动把类型定义文件加入:
// pageage.json
{
"files": [
"index.js",
"*.d.ts"
]
}
如果只发二级目录的话,把类型定义文件放到对应的二级目录下即可:
import { default as App } from 'demo/app'
发布到 @types organizatio
发布到 @types organizatio
的包表示源包没有包含类型定义文件,第三方/或原作者定义好类型定义文件之后,发布到 @types 中。例如 @types/express。
根据 DefinitelyTyped
的规则,和编辑器(和插件) 自动检测静态类型。
@types 下面的包是从 DefinitelyTyped 里自动发布的,通过 types-publisher 工具。
如果想让你的包发布为 @types 包,需要提交一个 pull request 到 https://github.com/DefinitelyTyped/DefinitelyTyped。
在这里查看详细信息 contribution guidelines page。
如果你正在使用 TypeScript,而使用了一些 JS 包并没有对应的类型定义文件,可以编写一份然后提交到 @types
。
赠人玫瑰,手留余香。
发布到 @types organizatio
的包可以通过 TypeSearch 搜索检索,使用 npm install --save-dev @types/xxxx
安装:
更多细节请参阅 DefinitelyTyped。
其他
module
通常来说,如果这份类型定义文件是 JS 库自带的,那么我们可以直接导出模块:
interface User {}
export = User
而如果这份类型定义文件不是 JS 库自带的,而是第三方的,则需要使用 module
进行关联。
例如 jquery
发布的 npm 包中不包含 *.d.ts
类型定义文件,jquery
的类型定义文件发布在了 @types/jquery
,所以类型定义文件中导出类型的时候,需要关联模块 jquery
,意思就是我专门针对这个包做的类型定义:
interface jQuery {}
declare module 'jquery' {
// module 中要使用 export = 而不是 export default
export = jQuery;
}
从而解决了一些主流的 JS 库发布的 npm
包中没有类型定义文件,但是我们可以用第三方类型定义文件为这些库补充类型。
风格
经过一系列探索,个人比较推荐下面的编写风格,先看目录:
types
├── application.d.ts
├── config.d.ts
├── index.d.ts # 入口模块
└── user.d.ts
入口模块主要做这些事情:
- 定义命名空间
- 导出和聚合子模块
主出口文件 index.d.ts
:
import * as UserModel from './user'
import * as AppModel from './application'
import * as ConfigModel from './config'
declare namespace Models {
export type User = UserModel.User;
export type Application = AppModel.Application;
// 利用 as 抹平争议性变量名
export type Config = ConfigModel.Config;
}
子模块无需定义命名空间,这样外部环境 (types
文件夹之外) 则无法获取子模块类型,达到了类型封闭的效果:
export interface User {
name: string;
age: number
}
JavaScript 和 TypeScript 交叉口 —— 类型定义文件(*.d.ts)的更多相关文章
- TypeScript 学习四 面向对象的特性,泛型,接口,模块,类型定义文件*.d.ts
1,面向对象的特性一:类,继承,见上一篇博客: 2,面向对象的特性二: 泛型(generic):参数化的类型,一般用来限制集合的内容:指定只能放某个类型的元素 如下图中的尖括号中的Person,就代表 ...
- Javascript和Typescript语言类型
静态语言(强类型语言) 静态语言是在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型. 例如:C++.Java.Delphi.C#等. 动态语言(弱类型语言) 动 ...
- TypeScript 类型定义文件(*.d.ts)自动生成工具
在开发ts时,有时会遇到没有d.ts文件的库,同时在老项目迁移到ts项目时也会遇到一些文件需要自己编写声明文件,但是在需要的声明文件比较多的情况,就需要自动生产声明文件.用过几个库.今天简单记录一下. ...
- TypeScript类型定义文件(*.d.ts)生成工具
在开发ts时,有时会遇到没有d.ts文件的库,同时在老项目迁移到ts项目时也会遇到一些文件需要自己编写声明文件,但是在需要的声明文件比较多的情况,就需要自动生产声明文件.用过几个库.今天简单记录一下. ...
- CesiumJS新增官方TypeScript类型定义
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 在当前的1.70版本中,CesiumJS现在附带了正式的Type ...
- JavaScript和TypeScript的区别和联系
转载自:http://web.jobbole.com/93618/?utm_source=group.jobbole.com&utm_medium=relatedArticles JavaSc ...
- TypeScript 条件类型精读与实践
在大多数程序中,我们必须根据输入做出决策.TypeScript 也不例外,使用条件类型可以描述输入类型与输出类型之间的关系. 本文同步首发在个人博客中,欢迎订阅.交流. 用于条件判断时的 extend ...
- 在 Typescript 2.0 中使用 @types 类型定义
在 Typescript 2.0 中使用 @type 类型定义 基于 Typescript 开发的时候,很麻烦的一个问题就是类型定义.导致在编译的时候,经常会看到一连串的找不到类型的提示.解决的方式经 ...
- 给JS包写TypeScript用的类型申明文件
TS (TypeScript)区别于JS (JavaScript)一个最大的不同是TS增加了类型.当一些TS代码要使用JS包的时候,最好这些JS包都有类型介绍,比如这个变量是什么类型,那个函数参数的什 ...
随机推荐
- Codevs1380没有上司的舞会_KEY
没有上司的舞会 1380 没有上司的舞会 时间限制: 1 s 空间限制: 128000 KB 题目描述 Description Ural大学有N个职员,编号为1~N.他们有从属关系,也就是说他们的关系 ...
- 安装myeclipse2015 stable 3.0破解之后发生出现SECURITY ALERT:iNTEGRITY CHECK ERROR然后闪退解决方案
安装好myeclipse2015 stable以后也一步步按着破解文件的步骤来进行.打开myEclipse---->Subscription information--->Subscrip ...
- 《effective Go》读后记录
一个在线的Go编译器 如果还没来得及安装Go环境,想体验一下Go语言,可以在Go在线编译器 上运行Go程序. 格式化 让所有人都遵循一样的编码风格是一种理想,现在Go语言通过gofmt程序,让机器来处 ...
- 洗礼灵魂,修炼python(4)--从简单案列中揭示常用内置函数以及数据类型
上一篇说到print语句,print是可以打印任何类型到屏幕上,都有哪些类型呢? 整形(int) 长整型(long) 浮点型(float) 字符型(str) 布尔型(bool) 最常见的就这几种. 在 ...
- Next Greater Element I
You are given two arrays (without duplicates) nums1 and nums2 where nums1's elements are subset of n ...
- SqlServer和Oracle中一些常用的sql语句9 SQL优化
--SQL查询优化 尽量避免使用or,not,distinct运算符,简化连接条件 /*Or运算符*/ use db_business go select * from 仓库 where 城市='北京 ...
- 使用Fabric一键批量部署上线/线上环境监控
本文讲述如何使用fabric进行批量部署上线的功能 这个功能对于小应用,可以避免开发部署上线的平台,或者使用linux expect开发不优雅的代码. 前提条件: 1.运行fabric脚本的机器和其他 ...
- JavaScript中的this基本问题
在函数中 this 到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了. 执行上下文环境 : **定义**:执行函数的时候,会产生一个上下文的对象,里面保存变量,函数声明和th ...
- CSS之 absoulte 属性
特性: absoulte 与 float 具有相同的特性:包裹性,与破坏性 absoulte 与 float 可以交替使用 不受 relative 限制的 absoulte 定位,行为表现上可以不 ...
- 使用vsftpd+nginx搭建一个文件服务器
一:安装vsftpd 1.安装 [root@localhost jack]# yum -y install vsftpd 安装完后,有/etc/vsftpd/vsftpd.conf 文件,是vsftp ...