在 《从 JavaScript 到 TypeScript 系列》 文章我们已经学习了 TypeScript 相关的知识。
TypeScript 的核心在于静态类型,我们在编写 TS 的时候会定义很多的类型,但是主流的库都是 JavaScript 编写的,并不支持类型系统。那么如何让这些第三方库也可以进行类型推导呢?

这篇文章我们来讲解 JavaScript 和 TypeScript 的静态类型交叉口 —— 类型定义文件。

这篇文章首发于我的个人博客 《听说》。

前端开发 QQ 群:377786580

类型定义文件

在 TypeScript 中,我们可以很简单的,在代码编写中定义类型:

interface IBaseModel {say(keys: string[] | null): object
}class User implements IBaseModel {name: stringconstructor (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 defaultexport = 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(): voidget(key: string): object
}

interface 和 type(或者说 class) 很像。

但是 type 的含义是定义自定义类型,当 TS 提供给你的基础类型都不满足的时候,可以使用 type 自由组合出你的新类型,而 interface 应该是对外输出的接口。

type 不可以被继承,但 interface 可以:

interface BaseApplication {appId: number
}export interface Application extends BaseApplication {init(): voidget(key: string): object
}

declare

declare 可以创建 *.d.ts 文件中的变量,declare 只能作用域最外层:

declare var foo: number;
declare function greet(greeting: string): void;declare namespace tasaid {// 这里不能 declareinterface 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 = objecttype 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

  1. 与你的 npm 包捆绑在一起(内置类型定义文件)
  2. 发布到 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 defaultexport = jQuery;
}

从而解决了一些主流的 JS 库发布的 npm 包中没有类型定义文件,但是我们可以用第三方类型定义文件为这些库补充类型。

风格

经过一系列探索,个人比较推荐下面的编写风格,先看目录:

types
├── application.d.ts
├── config.d.ts
├── index.d.ts # 入口模块
└── user.d.ts

入口模块主要做这些事情:

  1. 定义命名空间
  2. 导出和聚合子模块

主出口文件 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)相关推荐

  1. ts定义html是什么类型,TypeScript—类型定义文件(*.d.ts)

    一.ts文件中引入jquery. 1.大家是否有再vue 上使用过 ts,并再 .ts文件中引用过 jquery 1.1是不是遇到过如下问题: import $ from 'jquery'; /*** ...

  2. typescript 基础类型定义

    1.typescript 类型定义 代码如下(示例): //基础类型/*** 1. 布尔 boolean* 2. 数字 number* 3. 字符串 string* 4. 数组 array* 5. 元 ...

  3. TypeScript 类型声明文件.d.ts

    文章目录 一.TS 中的两种文件类型 二.类型声明文件的使用说明 1. 使用已有的类型声明文件 1.1 内置类型声明文件 1.2 第三方库类型声明文件 1.2.1 库自带声明文件 1.2.2 Defi ...

  4. 离线配置xml的文档类型定义文件(xml语法规则) dtd

    step1)将jar文件解压,并找到对应的 dtd文件: step2)不带引号复制 dtd uri: http://mybatis.org/dtd/mybatis-3-config.dtd step3 ...

  5. JavaScript 和 typeScript 中的 import、from

    From:https://segmentfault.com/a/1190000018249137?utm_source=tag-newest Github - allowSyntheticDefaul ...

  6. typescript(四)ts中函数的参数和返回值的类型定义

    前面我们讲到过ts的静态类型定义中的函数类型定义,先来回顾下: const fnA: () => string = () => { return '1' } const fnB: () = ...

  7. typescript索引类型_TypeScript的索引类型与映射类型,以及常用工具泛型的实现

    相信现在很多小伙伴都在使用 TypeScript(以下简称 TS),在 TS 中除了一些常用的基本类型外,还有一些稍微高级一点的类型,这些就是我本次文章要讲的内容:索引类型与映射类型,希望小伙伴们看过 ...

  8. Typescript基础类型以及与Javascript对比

    TypeScript数据类型以及与JavaScript对比 文章目录 TypeScript数据类型以及与JavaScript对比 介绍 一.数据类型与基础数据类型 1.数据类型 2.基础数据类型 3. ...

  9. HTML文件类型定义

    一,HTML文件类型定义 1,<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"        &q ...

最新文章

  1. JSON学习笔记-3
  2. Spring AOP 源码分析 - 创建代理对象
  3. 【工业控制】什么是波形
  4. 如何在Hybris commerce里创建一个media对象
  5. 第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)
  6. Cucumber+Rest Assured快速搭建api自动化测试平台
  7. Chrome划词翻译插件
  8. 字符串处理 —— 单模式匹配 —— 朴素的字符串匹配算法(BF 算法)
  9. java super.start,java – 在字节码中确定哪里是super()方法调用所有构造函数必须在JVM上执行...
  10. 职业学校计算机知识试卷答案,2016中等职业学校计算机等级考试题库(含答案)计算机基础题库...
  11. 如何一站式高效管理固定资产?
  12. arduino教程汇总
  13. 计算机的了解以及组装
  14. c++ 智能指针 (std::weak_ptr)(一)
  15. 帝国php漏洞,帝国cms远程代码执行漏洞-1
  16. Echarts 漏斗图
  17. 基于MATLAB的电弧仿真模型(Mayr/Cassie 电弧模型)
  18. Docker容器修改配置文件
  19. 删除SQL表中的某一列
  20. Windows Server 2008 安装SVN

热门文章

  1. vivo图像算法工程师双非研究生可以吗_我的自动驾驶工程师成长之路
  2. python毕业设计作品基于django框架 校园运动场地预约系统毕设成品(7)中期检查报告
  3. Android 模仿微信读书左右对齐的文字效果
  4. android 查询快捷方式,android桌面快捷方式的创建和查询
  5. matlab传热分析,基于matlab的超临界流体对流传热可视化分析方法
  6. Java实验项目二打印万年历
  7. __attribute__ 详解 1
  8. 俞敏洪的同济大学演讲
  9. C# 读写文件时抛出异常“System.IO.IOException: 文件“xxx”正由另一进程使用,因此该进程”
  10. android 10 上传图片问题