為TypeScript引用的JS寫聲明文件
寫TypeScript聲明文件的時候會有三個困惑,一個是聲明文件是什么?一個是聲明文件怎么寫?還有一個是TS依據(jù)什么規(guī)則找到我們的聲明文件或者說模塊。
第一個問題:按照我的理解聲明文件就是告訴TS編譯器有哪些模塊?有哪些變量?變量分別是什么類型?所以如果說原本就是TS寫的代碼這些都是具有的,但是JS寫的代碼就不會有這些,因為這些強類型是TS對JS的擴展,JS沒有這個特性。
第二個問題:這個需要看官方的文檔(下文也簡單舉例一些例子),并且要仔細看,如果語法錯誤也會帶來很多困擾。
第三個問題:這個涉及到TS的模塊解析,詳情請看?官文–模塊解析?,這篇文檔寫的很詳細,足以解釋。這里列舉兩個我認為的重點:相對模塊導入不能解析為一個外部模塊聲明。自己聲明的模塊(declare module “name”{} 這個name不能是相對的也就是說不能以?./?../?/開頭),還有一個是配置文件tsconfig.json文件中{"compilerOptions": {"traceResolution": true}}可以追蹤模塊解析的過程,想要深入了解這是一個好的方法,但是據(jù)我觀察只能在vscode控制臺中才能打印出來。
**注:TS引用JS庫如果JS庫沒有對應的聲明文件編譯器是不會報錯的,因為沒有聲明文件的JS模塊會隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true?這樣的配置。**
已經(jīng)過驗證上面的解釋是沒錯的還原如下:
注:TS引用JS庫如果JS庫沒有對應的聲明文件編譯器是不會報錯的,因為沒有聲明文件的JS模塊會隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true?這樣的配置,指明了如有隱式轉(zhuǎn)換為any類型就報錯,才會報錯。
TypeScript是JavaScript的超集
TypeScript會進類型檢查,什么鬼?JS沒有這個東西
使用TS進行開發(fā)也可以使用當前豐富的JS庫,很多JS庫有寫好的TS聲明文件,但是如果是我們自己寫的JS庫想要在TS中使用就需要我們自己去編寫聲明文件(.d.ts文件),怎么寫?這就是極具個人經(jīng)驗主義的本文要解釋的問題,如有謬誤感謝指正。
本文主要是對此刻所得的整理。
下面示例基于webpack配合ts-loader,開發(fā)環(huán)境的配置可以參考我的另一篇文章基于webpack3.x從0開始搭建React開發(fā)環(huán)境。
現(xiàn)有的JS庫可能有不同的寫法,有的庫導出屬性,方法等,有的庫導出一個類還有的庫只導出一個函數(shù)。下面針對不同類型的JS庫來寫不同的聲明文件。
聲明文件也分為兩種,一種是全局類型聲明另一種是模塊導出聲明。而這兩種只是聲明文件的寫法和JS庫的寫法沒有關系,并不是說全局的庫就需要使用全局類型聲明的寫法,模塊的庫就用模塊導出的寫法。
// 文件目錄結構如下-- project |-- node_modules? |-- simple? ? |-- index.js? ? |-- lib1.js? ? |-- lib2.js |-- src? |-- types.d.ts? |-- app.ts
// /node_modules/simple/index.js// ES原生模塊寫法,并且導出了屬性和方法leta =1;functiongeta(){returna;}functionseta(val){a = val}export {geta, seta, a,default: a}
// 為simple/index.js寫全局類型聲明,在types.d.ts中添加如下代碼declare module"simple"{? let a: number;? exportfunctiongeta(): void;exportfunctionseta(n: number): void;exportdefaulta;}
// app.ts? 使用三斜線指令引入聲明文件///import a, {geta, seta}from'simple'console.log(a)
// /node_modules/simple/lib1.js// 導出一個類functionAb(){this.a =1}Ab.prototype.seta =function(num){this.a = num}Ab.prototype.geta =function(num){returnthis.a}exports.Ab = Ab
// 在type.d.ts文件中添加declare module"simple/lib1"{? exportclassAb {? ? private a;? ? seta(n: number):void;? ? geta(): number? }}
// app.ts? 使用三斜線指令引入聲明文件/// importa, {geta, seta}from'simple'import {Ab}from'simple/lib1'// 得以驗證console.log(newAb())console.log(a)
// /node_modules/simple/lib2.js// 只導出一個函數(shù)module.exports =functiongetRandom(){returnMath.random()}
//在type.d.ts文件中添加declaremodule"simple/lib2"{letgetRandom:()=>number;export= getRandom;}
// app.ts? 使用三斜線指令引入聲明文件/// import a, {geta, seta} from'simple'import {Ab} from'simple/lib1'import getRandom =require('simple/lib2')// 得以驗證console.log(getRandom())console.log(newAb())console.log(a)
上面給出的只是全局聲明的寫法,下面會針對上面的js庫重新?lián)Q成模塊導出聲明的寫法
目錄改成如下形式,app.ts文件無需做大的改動只需要將三斜線指令去除即可,一般情況下即使去除該指令types.d.ts文件還在的話TypeScript編譯器還是會將該文件加載編譯,這與配置有關。
并且根據(jù)我的觀察發(fā)現(xiàn),修改聲明文件并不會馬上起作用,比如在聲明文件中加了一個方法,在使用的時候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.tslet a: number;exportfunctiongeta(): void;exportfunctionseta(n: number): void;exportdefaulta;
// app.tsimporta, {geta, seta}from'simple'// 得以驗證console.log(geta())
//lib1.d.tsfunctionAb(){this.a =1}Ab.prototype.seta =function(num){this.a = num}Ab.prototype.geta =function(){returnthis.a}exports.Ab = Ab
importa, {geta, seta}from'simple'import {Ab}from'simple/lib1'// 得以驗證console.log(newAb().geta())console.log(geta())
//lib2.d.tsletgetRandom:()=>number;export= getRandom;
importa, {geta, seta}from'simple'import {Ab}from'simple/lib1'import getRandom =require('simple/lib2')// 得以驗證console.log(getRandom())console.log(newAb().geta())console.log(geta())
在全局類型聲明的文件中聲明一個模塊,模塊什么都不做即可(這里還可以更加徹底,如文章開頭的更新):
// types.d.ts? 替換simple聲明如下decalremodule"simple"
默認所有可見的”@types”包會在編譯過程中被包含進來。什么叫默認可見?就是說node_modules/@types文件夾及他的子文件夾下所有的包都是可見的,還包括?../node_modules/@types和../../node_modules/@types等。
有什么用呢?可以將自己的全局聲明文件放在這個文件夾里面,這樣就可以自動加載。
上面一段話也是錯的,并不會自動的導入,所上面摘抄官網(wǎng)的一段話到底啥意思就不得而知了??赡芤馑际菚话蔷涂茨懿荒苷_解析了。
注:node_modules/@types 是TS聲明文件默認位置,并且只能是全局聲明的寫法。
上面一句話就錯了,@types里面的聲明可以是模塊的寫法,也可以是全局寫法。只要是一個模塊(頂層的import/export這個文件就是一個模塊或者declare “name” module { export … })就行了。
在@types文件夾下的聲明的包和在node_modules下的包其實一樣,在node_modules沒有解析到合適的.ts/.tsx/.d.ts 文件之后,TS編譯器便會來到node_modules/@types文件加下尋找,如下:
import{a}from'abc'
這種情況@/types/abc.d.ts有兩種寫法
exportconsta: number;// 只要導出即可
declaremodule"abc"{exportconsta: number;}
同樣這個@/types/abc.d.ts的目錄也可以這樣:
@types/abc/index.d.ts
這個配置有兩個:一個是typeRoots表明聲明文件的根文件夾,還有一個是types表明需要包含的聲明文件包(文件夾名,我試過types/efg.d.ts 這樣的并不能用,需要這樣types/efg/index.d.ts)。
上面劃掉的部分是錯的,可以用types/efg.d.ts這樣的。
注驗證的時候最好配合types因為上文提到過,TS編譯器會默認包含所有的ts文件,所以如果不過濾,設置的typeRoots沒有意義因為默認就是全部包含的。
觀察上面模塊導出聲明和全局類型聲明兩種寫法發(fā)現(xiàn)寫法差別并不大,主要區(qū)別就是聲明文件放置位置不同,全局會多一個declare module "name1"?。再仔細觀察會發(fā)現(xiàn)這個name1和import … from "name2"?中的name2是一樣的,然后對于全局的聲明文件還在需要的時候使用?/// ?引用進來。所以我懷疑(因為我還沒有了解到是不是事實)import … from "name"?這個其實引用的是我們在聲明文件中定義的module。
如果一個JS文件在頂層具有import或者export那么這個文件就是一個模塊(模塊名對應的就是文件名),在模塊中定義的變量并不會暴露在全局環(huán)境下。
而上面模塊導出的寫法?declare module "name"{}?就相當于聲明了一個模塊(一個文件?)。
全局類型聲明中只是聲明了相關模塊當然也可以聲明其他東西,而是用全局類型聲明的方法,不是import(這不是一個模塊)而是三斜線指令使用?/// ?這樣 .d.ts 文件中的聲明就被編譯器讀取了,之后可以再下面import … from "module"?只是這個module是我們聲明出來的,并不會在對應的路徑下找到相關的?.d.ts?或?.ts?或?.tsx文件。
如果是模塊導出寫法必須和庫在一起,否則并不知道屬于哪個模塊的聲明,但是@types怎么解釋
模塊導出聲明的寫法是在 .d.ts 文件頂層是有export的所以一個文件是一個模塊,如果單獨引入(要使用import來引入)模塊的話并不知道這個模塊是哪個庫的聲明文件,所以需要和JS庫放在一起,并且名字還要一樣(?后綴名不一樣)。但是@types怎么解釋???
見上文node_modules下的@types文件夾
TypeScript 的兩種聲明文件寫法的區(qū)別和根本意義
原文引用自:https://blog.csdn.net/letterTiger/article/details/80596369