javascript
TS引用JS模块
為TypeScript引用的JS寫聲明文件
寫TypeScript聲明文件的時候會有三個困惑,一個是聲明文件是什么?一個是聲明文件怎么寫?還有一個是TS依據什么規則找到我們的聲明文件或者說模塊。
第一個問題:按照我的理解聲明文件就是告訴TS編譯器有哪些模塊?有哪些變量?變量分別是什么類型?所以如果說原本就是TS寫的代碼這些都是具有的,但是JS寫的代碼就不會有這些,因為這些強類型是TS對JS的擴展,JS沒有這個特性。
第二個問題:這個需要看官方的文檔(下文也簡單舉例一些例子),并且要仔細看,如果語法錯誤也會帶來很多困擾。
第三個問題:這個涉及到TS的模塊解析,詳情請看 官文–模塊解析 ,這篇文檔寫的很詳細,足以解釋。這里列舉兩個我認為的重點:相對模塊導入不能解析為一個外部模塊聲明。自己聲明的模塊(declare module “name”{} 這個name不能是相對的也就是說不能以 ./``````../``````/開頭),還有一個是配置文件tsconfig.json文件中{"compilerOptions": {"traceResolution": true}}可以追蹤模塊解析的過程,想要深入了解這是一個好的方法,但是據我觀察只能在vscode控制臺中才能打印出來。
注:TS引用JS庫如果JS庫沒有對應的聲明文件編譯器是不會報錯的,因為沒有聲明文件的JS模塊會隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true這樣的配置。
已經過驗證上面的解釋是沒錯的還原如下:
注:TS引用JS庫如果JS庫沒有對應的聲明文件編譯器是不會報錯的,因為沒有聲明文件的JS模塊會隱式的獲得any類型,除非tsconfig.json中有noImplicitAny: true這樣的配置,指明了如有隱式轉換為any類型就報錯,才會報錯。
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, 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 // 導出一個類 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())不想為JS類庫寫具體的聲明文件怎么辦?
在全局類型聲明的文件中聲明一個模塊,模塊什么都不做即可(這里還可以更加徹底,如文章開頭的更新):
// types.d.ts 替換simple聲明如下 decalre module "simple"node_modules下的@types文件夾
默認所有可見的"@types"包會在編譯過程中被包含進來。什么叫默認可見?就是說node_modules/@types文件夾及他的子文件夾下所有的包都是可見的,還包括 ../node_modules/@types和../../node_modules/@types等。
有什么用呢?可以將自己的全局聲明文件放在這個文件夾里面,這樣就可以自動加載。
上面一段話也是錯的,并不會自動的導入,所上面摘抄官網的一段話到底啥意思就不得而知了。可能意思是會被包含但是就看能不能正確解析了。
注:node_modules/@types 是TS聲明文件默認位置,并且只能是全局聲明的寫法。
上面一句話就錯了,@types里面的聲明可以是模塊的寫法,也可以是全局寫法。只要是一個模塊(頂層的import/export這個文件就是一個模塊或者declare “name” module { export … })就行了。
全部重寫node_modules下的@types文件夾如下
在@types文件夾下的聲明的包和在node_modules下的包其實一樣,在node_modules沒有解析到合適的.ts/.tsx/.d.ts 文件之后,TS編譯器便會來到node_modules/@types文件加下尋找,如下:
import {a} from 'abc'這種情況@/types/abc.d.ts有兩種寫法
export const a: number; // 只要導出即可 declare module "abc"{export const a: number; }同樣這個@/types/abc.d.ts的目錄也可以這樣:
@types/abc/index.d.ts
關于@types的配置
這個配置有兩個:一個是typeRoots表明聲明文件的根文件夾,還有一個是types表明需要包含的聲明文件包(文件夾名,我試過types/efg.d.ts 這樣的并不能用,需要這樣types/efg/index.d.ts)。
上面劃掉的部分是錯的,可以用types/efg.d.ts這樣的。
注驗證的時候最好配合types因為上文提到過,TS編譯器會默認包含所有的ts文件,所以如果不過濾,設置的typeRoots沒有意義因為默認就是全部包含的。
極具個人經驗部分
觀察上面模塊導出聲明和全局類型聲明兩種寫法發現寫法差別并不大,主要區別就是聲明文件放置位置不同,全局會多一個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的思考
見上文node_modules下的@types文件夾
參考
TypeScript 的聲明文件的使用與編寫
TypeScript 的兩種聲明文件寫法的區別和根本意義
TypeScript文檔
TS模塊解析
總結
- 上一篇: 基于webpack3.x从0开始搭建Re
- 下一篇: JS中与正则相关的方法