TypeScript是JavaScript的超集
TypeScript会进类型检查,什么鬼?JS没有这个东西
使用TS进行开发也可以使用当前丰富的JS库,很多JS库有写好的TS声明文件,但是如果是我们自己写的JS库想要在TS中使用就需要我们自己去编写声明文件(.d.ts文件),怎么写?这就是极具个人经验主义的本文要解释的问题,如有谬误感谢指正。
本文主要是对此刻所得的整理。
下面示例基于webpack配合ts-loader,开发环境的配置可以参考我的另一篇文章基于webpack3.x从0开始搭建React开发环境。
JS库文件和对应的TS声明文件
现有的JS库可能有不同的写法,有的库导出属性,方法等,有的库导出一个类还有的库只导出一个函数。下面针对不同类型的JS库来写不同的声明文件。
声明文件也分为两种,一种是全局类型声明另一种是模块导出声明。而这两种只是声明文件的写法和JS库的写法没有关系,并不是说全局的库就需要使用全局类型声明的写法,模块的库就用模块导出的写法。
// 文件目录结构如下
-- project
|-- node_modules
|-- simple
|-- index.js
|-- lib1.js
|-- lib2.js
|-- src
|-- types.d.ts
|-- app.ts
// /node_modules/simple/index.js
// ES原生模块写法,并且导出了属性和方法
let a = 1;
function geta () {
return a;
}
function seta (val) {
a = val
}
export {geta, seta, a, default: a}
// 为simple/index.js写全局类型声明,在types.d.ts中添加如下代码
declare module "simple" {
let a: number;
export function geta(): void;
export function seta(n: number): void;
export default a;
}
// app.ts 使用三斜线指令引入声明文件
/// <reference path="./type.d.ts" />
import a, {geta, seta} from 'simple'
console.log(a)
// /node_modules/simple/lib1.js
// 导出一个类
function Ab () {
this.a = 1
}
Ab.prototype.seta = function (num) {
this.a = num
}
Ab.prototype.geta = function (num) {
return this.a
}
exports.Ab = Ab
// 在type.d.ts文件中添加
declare module "simple/lib1" {
export class Ab {
private a;
seta(n: number): void;
geta(): number
}
}
// app.ts 使用三斜线指令引入声明文件
/// <reference path="./type.d.ts" />
import a, {geta, seta} from 'simple'
import {Ab} from 'simple/lib1'
// 得以验证
console.log(new Ab())
console.log(a)
// /node_modules/simple/lib2.js
// 只导出一个函数
module.exports = function getRandom () {
return Math.random()
}
// 在type.d.ts文件中添加
declare module "simple/lib2" {
let getRandom: () => number;
export = getRandom;
}
// app.ts 使用三斜线指令引入声明文件
/// <reference path="./type.d.ts" />
import a, {geta, seta} from 'simple'
import {Ab} from 'simple/lib1'
import getRandom = require('simple/lib2')
// 得以验证
console.log(getRandom())
console.log(new Ab())
console.log(a)
上面给出的只是全局声明的写法,下面会针对上面的js库重新换成模块导出声明的写法
目录改成如下形式,app.ts文件无需做大的改动只需要将三斜线指令去除即可,一般情况下即使去除该指令types.d.ts文件还在的话TypeScript编译器还是会将该文件加载编译,这与配置有关。
并且根据我的观察发现,修改声明文件并不会马上起作用,比如在声明文件中加了一个方法,在使用的时候TypeScript编译器还是会报错说这个类型没有这个方法,需要重启webpack-dev-server(我用的是这个)
// 文件目录结构如下
-- project
|-- node_modules
|-- simple
|-- index.js
|-- index.d.ts
|-- lib1.js
|-- lib1.d.ts
|-- lib2.js
|-- lib2.d.ts
|-- src
|-- app.ts
// index.d.ts
let a: number;
export function geta(): void;
export function seta(n: number): void;
export default a;
// app.ts
import a, {geta, seta} from 'simple'
// 得以验证
console.log(geta())
//lib1.d.ts
function Ab () {
this.a = 1
}
Ab.prototype.seta = function (num) {
this.a = num
}
Ab.prototype.geta = function () {
return this.a
}
exports.Ab = Ab
import a, {geta, seta} from 'simple'
import {Ab} from 'simple/lib1'
// 得以验证
console.log(new Ab().geta())
console.log(geta())
// lib2.d.ts
let getRandom: () => number;
export = getRandom;
import a, {geta, seta} from 'simple'
import {Ab} from 'simple/lib1'
import getRandom = require('simple/lib2')
// 得以验证
console.log(getRandom())
console.log(new Ab().geta())
console.log(geta())
极具个人经验部分
观察上面模块导出声明和全局类型声明两种写法发现写法差别并不大,主要区别就是声明文件放置位置不同,全局会多一个declare module "name1"
。再仔细观察会发现这个name1和import … from "name2"
中的name2是一样的,然后对于全局的声明文件还在需要的时候使用 /// <reference path = "path" />
引用进来。所以我怀疑(因为我还没有了解到是不是事实)import … from "name"
这个其实引用的是我们在声明文件中定义的module。
什么是module?
如果一个JS文件在顶层具有import或者export那么这个文件就是一个模块(模块名对应的就是文件名),在模块中定义的变量并不会暴露在全局环境下。
而上面模块导出的写法 declare module "name"{}
就相当于声明了一个模块(一个文件?)。
全局类型声明的思考
全局类型声明中只是声明了相关模块当然也可以声明其他东西,而是用全局类型声明的方法,不是import(这不是一个模块)而是三斜线指令使用 /// <reference path="path" />
这样 .d.ts 文件中的声明就被编译器读取了,之后可以再下面import … from "module"
只是这个module是我们声明出来的,并不会在对应的路径下找到相关的 .d.ts
或 .ts
或 .tsx
文件。
如果是模块导出写法必须和库在一起,否则并不知道属于哪个模块的声明,但是@types怎么解释
模块导出声明的思考
模块导出声明的写法是在 .d.ts 文件顶层是有export的所以一个文件是一个模块,如果单独引入(要使用import来引入)模块的话并不知道这个模块是哪个库的声明文件,所以需要和JS库放在一起,并且名字还要一样(后缀名不一样)。但是@types怎么解释???
关于@types的思考
留坑。。。