javascript
TS引用JS模块
為TypeScript引用的JS寫聲明文件
寫TypeScript聲明文件的時(shí)候會(huì)有三個(gè)困惑,一個(gè)是聲明文件是什么?一個(gè)是聲明文件怎么寫?還有一個(gè)是TS依據(jù)什么規(guī)則找到我們的聲明文件或者說模塊。
第一個(gè)問題:按照我的理解聲明文件就是告訴TS編譯器有哪些模塊?有哪些變量?變量分別是什么類型?所以如果說原本就是TS寫的代碼這些都是具有的,但是JS寫的代碼就不會(huì)有這些,因?yàn)檫@些強(qiáng)類型是TS對(duì)JS的擴(kuò)展,JS沒有這個(gè)特性。
第二個(gè)問題:這個(gè)需要看官方的文檔(下文也簡(jiǎn)單舉例一些例子),并且要仔細(xì)看,如果語法錯(cuò)誤也會(huì)帶來很多困擾。
第三個(gè)問題:這個(gè)涉及到TS的模塊解析,詳情請(qǐng)看 官文–模塊解析 ,這篇文檔寫的很詳細(xì),足以解釋。這里列舉兩個(gè)我認(rèn)為的重點(diǎn):相對(duì)模塊導(dǎo)入不能解析為一個(gè)外部模塊聲明。自己聲明的模塊(declare module “name”{} 這個(gè)name不能是相對(duì)的也就是說不能以 ./``````../``````/開頭),還有一個(gè)是配置文件tsconfig.json文件中{"compilerOptions": {"traceResolution": true}}可以追蹤模塊解析的過程,想要深入了解這是一個(gè)好的方法,但是據(jù)我觀察只能在vscode控制臺(tái)中才能打印出來。
注:TS引用JS庫如果JS庫沒有對(duì)應(yīng)的聲明文件編譯器是不會(huì)報(bào)錯(cuò)的,因?yàn)闆]有聲明文件的JS模塊會(huì)隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true這樣的配置。
已經(jīng)過驗(yàn)證上面的解釋是沒錯(cuò)的還原如下:
注:TS引用JS庫如果JS庫沒有對(duì)應(yīng)的聲明文件編譯器是不會(huì)報(bào)錯(cuò)的,因?yàn)闆]有聲明文件的JS模塊會(huì)隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true這樣的配置,指明了如有隱式轉(zhuǎn)換為any類型就報(bào)錯(cuò),才會(huì)報(bào)錯(cuò)。
TypeScript是JavaScript的超集
TypeScript會(huì)進(jìn)類型檢查,什么鬼?JS沒有這個(gè)東西
使用TS進(jìn)行開發(fā)也可以使用當(dāng)前豐富的JS庫,很多JS庫有寫好的TS聲明文件,但是如果是我們自己寫的JS庫想要在TS中使用就需要我們自己去編寫聲明文件(.d.ts文件),怎么寫?這就是極具個(gè)人經(jīng)驗(yàn)主義的本文要解釋的問題,如有謬誤感謝指正。
本文主要是對(duì)此刻所得的整理。
下面示例基于webpack配合ts-loader,開發(fā)環(huán)境的配置可以參考我的另一篇文章基于webpack3.x從0開始搭建React開發(fā)環(huán)境。
JS庫文件和對(duì)應(yīng)的TS聲明文件
現(xiàn)有的JS庫可能有不同的寫法,有的庫導(dǎo)出屬性,方法等,有的庫導(dǎo)出一個(gè)類還有的庫只導(dǎo)出一個(gè)函數(shù)。下面針對(duì)不同類型的JS庫來寫不同的聲明文件。
聲明文件也分為兩種,一種是全局類型聲明另一種是模塊導(dǎo)出聲明。而這兩種只是聲明文件的寫法和JS庫的寫法沒有關(guān)系,并不是說全局的庫就需要使用全局類型聲明的寫法,模塊的庫就用模塊導(dǎo)出的寫法。
// 文件目錄結(jié)構(gòu)如下 -- project|-- node_modules|-- simple|-- index.js|-- lib1.js|-- lib2.js|-- src|-- types.d.ts|-- app.ts // /node_modules/simple/index.js // ES原生模塊寫法,并且導(dǎo)出了屬性和方法 let a = 1;function geta () {return a; }function seta (val) {a = val }export {geta, seta, a, a as default} // 為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 // 導(dǎo)出一個(gè)類 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'// 得以驗(yàn)證 console.log(new Ab())console.log(a) // /node_modules/simple/lib2.js // 只導(dǎo)出一個(gè)函數(shù) 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')// 得以驗(yàn)證 console.log(getRandom())console.log(new Ab())console.log(a)上面給出的只是全局聲明的寫法,下面會(huì)針對(duì)上面的js庫重新?lián)Q成模塊導(dǎo)出聲明的寫法
目錄改成如下形式,app.ts文件無需做大的改動(dòng)只需要將三斜線指令去除即可,一般情況下即使去除該指令types.d.ts文件還在的話TypeScript編譯器還是會(huì)將該文件加載編譯,這與配置有關(guān)。
并且根據(jù)我的觀察發(fā)現(xiàn),修改聲明文件并不會(huì)馬上起作用,比如在聲明文件中加了一個(gè)方法,在使用的時(shí)候TypeScript編譯器還是會(huì)報(bào)錯(cuò)說這個(gè)類型沒有這個(gè)方法,需要重啟webpack-dev-server(我用的是這個(gè))
// 文件目錄結(jié)構(gòu)如下 -- 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'// 得以驗(yàn)證 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'// 得以驗(yàn)證 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')// 得以驗(yàn)證 console.log(getRandom())console.log(new Ab().geta())console.log(geta())不想為JS類庫寫具體的聲明文件怎么辦?
在全局類型聲明的文件中聲明一個(gè)模塊,模塊什么都不做即可(這里還可以更加徹底,如文章開頭的更新):
// types.d.ts 替換simple聲明如下 decalre module "simple"node_modules下的@types文件夾
默認(rèn)所有可見的"@types"包會(huì)在編譯過程中被包含進(jìn)來。什么叫默認(rèn)可見?就是說node_modules/@types文件夾及他的子文件夾下所有的包都是可見的,還包括 ../node_modules/@types和../../node_modules/@types等。
有什么用呢?可以將自己的全局聲明文件放在這個(gè)文件夾里面,這樣就可以自動(dòng)加載。
上面一段話也是錯(cuò)的,并不會(huì)自動(dòng)的導(dǎo)入,所上面摘抄官網(wǎng)的一段話到底啥意思就不得而知了。可能意思是會(huì)被包含但是就看能不能正確解析了。
注:node_modules/@types 是TS聲明文件默認(rèn)位置,并且只能是全局聲明的寫法。
上面一句話就錯(cuò)了,@types里面的聲明可以是模塊的寫法,也可以是全局寫法。只要是一個(gè)模塊(頂層的import/export這個(gè)文件就是一個(gè)模塊或者declare “name” module { export … })就行了。
全部重寫node_modules下的@types文件夾如下
在@types文件夾下的聲明的包和在node_modules下的包其實(shí)一樣,在node_modules沒有解析到合適的.ts/.tsx/.d.ts 文件之后,TS編譯器便會(huì)來到node_modules/@types文件加下尋找,如下:
import {a} from 'abc'這種情況@/types/abc.d.ts有兩種寫法
export const a: number; // 只要導(dǎo)出即可 declare module "abc"{export const a: number; }同樣這個(gè)@/types/abc.d.ts的目錄也可以這樣:
@types/abc/index.d.ts
關(guān)于@types的配置
這個(gè)配置有兩個(gè):一個(gè)是typeRoots表明聲明文件的根文件夾,還有一個(gè)是types表明需要包含的聲明文件包(文件夾名,我試過types/efg.d.ts 這樣的并不能用,需要這樣types/efg/index.d.ts)。
上面劃掉的部分是錯(cuò)的,可以用types/efg.d.ts這樣的。
注驗(yàn)證的時(shí)候最好配合types因?yàn)樯衔奶岬竭^,TS編譯器會(huì)默認(rèn)包含所有的ts文件,所以如果不過濾,設(shè)置的typeRoots沒有意義因?yàn)槟J(rèn)就是全部包含的。
極具個(gè)人經(jīng)驗(yàn)部分
觀察上面模塊導(dǎo)出聲明和全局類型聲明兩種寫法發(fā)現(xiàn)寫法差別并不大,主要區(qū)別就是聲明文件放置位置不同,全局會(huì)多一個(gè)declare module "name1"。再仔細(xì)觀察會(huì)發(fā)現(xiàn)這個(gè)name1和import … from "name2"中的name2是一樣的,然后對(duì)于全局的聲明文件還在需要的時(shí)候使用 /// <reference path = "path" />引用進(jìn)來。所以我懷疑(因?yàn)槲疫€沒有了解到是不是事實(shí))import … from "name"這個(gè)其實(shí)引用的是我們?cè)诼暶魑募卸x的module。
什么是module?
如果一個(gè)JS文件在頂層具有import或者export那么這個(gè)文件就是一個(gè)模塊(模塊名對(duì)應(yīng)的就是文件名),在模塊中定義的變量并不會(huì)暴露在全局環(huán)境下。
而上面模塊導(dǎo)出的寫法 declare module "name"{}就相當(dāng)于聲明了一個(gè)模塊(一個(gè)文件?)。
全局類型聲明的思考
全局類型聲明中只是聲明了相關(guān)模塊當(dāng)然也可以聲明其他東西,而是用全局類型聲明的方法,不是import(這不是一個(gè)模塊)而是三斜線指令使用 /// <reference path="path" />這樣 .d.ts 文件中的聲明就被編譯器讀取了,之后可以再下面import … from "module"只是這個(gè)module是我們聲明出來的,并不會(huì)在對(duì)應(yīng)的路徑下找到相關(guān)的 .d.ts或 .ts或 .tsx文件。
如果是模塊導(dǎo)出寫法必須和庫在一起,否則并不知道屬于哪個(gè)模塊的聲明,但是@types怎么解釋
模塊導(dǎo)出聲明的思考
模塊導(dǎo)出聲明的寫法是在 .d.ts 文件頂層是有export的所以一個(gè)文件是一個(gè)模塊,如果單獨(dú)引入(要使用import來引入)模塊的話并不知道這個(gè)模塊是哪個(gè)庫的聲明文件,所以需要和JS庫放在一起,并且名字還要一樣(后綴名不一樣)。但是@types怎么解釋???
關(guān)于@types的思考
見上文node_modules下的@types文件夾
參考
TypeScript 的聲明文件的使用與編寫
TypeScript 的兩種聲明文件寫法的區(qū)別和根本意義
TypeScript文檔
TS模塊解析
總結(jié)
- 上一篇: 基于webpack3.x从0开始搭建Re
- 下一篇: JS中与正则相关的方法