<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
宣告檔案是以.d.ts為字尾的檔案,開發者在宣告檔案中編寫型別宣告,TypeScript根據宣告檔案的內容進行型別檢查。(注意同目錄下最好不要有同名的.ts檔案和.d.ts,例如lib.ts和lib.d.ts,否則模組系統無法只根據檔名載入模組)
為什麼需要宣告檔案呢?我們知道TypeScript根據型別宣告進行型別檢查,但有些情況可能沒有型別宣告:
如果沒有型別宣告,在使用變數、呼叫函數、範例化類的時候就沒法通過TypeScript的型別檢查。
宣告檔案就是針對這些情況,開發者在宣告檔案中編寫第三方模組的型別宣告/宿主環境的型別宣告。讓TypeScript可以正常地進行型別檢查。
除此之外,宣告檔案也可以被匯入,使用其中暴露的型別定義。
總之,宣告檔案有兩種用法:
對於第二種用法,宣告檔案如何同相關模組關聯呢?
比如有個第三方包名字叫"foo",那麼TypeScript會在node_modules/foo中根據其package.json的types和typing欄位查詢宣告檔案查詢到的宣告檔案被作為該模組的宣告檔案;TypeScript也會在node_modules/@types/foo/目錄中查詢宣告檔案,如果能找到就被作為foo模組的宣告檔案;TypeScript還會在我們的專案中查詢.d.ts檔案,如果遇到declare module 'foo'語句,則該宣告被用作foo模組的宣告。
總結一下,TypeScript會在特定的目錄讀取指定的宣告檔案。
宣告檔案中的程式碼不會出現在最終的編譯結果中,編譯後會把轉換後的JavaScript程式碼輸出到"outDir"選項指定的目錄中,並且把 .ts模組中使用到的值的宣告都輸出到"declarationDir"指定的目錄中。
而在.ts檔案中的宣告語句,編譯後會被去掉,如
declare let a: number; export default a;
會被編譯為
"use strict"; exports.__esModule = true; exports["default"] = a;
TypeScript編譯過程不僅將TypeScript語法轉譯為ES6/ES5,還會將程式碼中.ts檔案中用到的值的型別輸出到指定的宣告檔案中。如果你需要實現一個庫專案,這個功能很有用,因為用到你的庫的專案可以直接使用這些宣告檔案,而不需要你再為你的庫寫宣告檔案。
TypeScript中的宣告會建立以下三種實體之一:名稱空間,型別或值。
名稱空間最終被編譯為全域性變數,因此我們也可以認為宣告檔案中其實建立了型別和值兩種實體。即定義型別或者宣告值。
// 型別 介面 interface Person {name: string;} // 型別 型別別名 type Fruit = {size: number}; // 值 變數 declare let a: number; // 值 函數 declare function log(message: string): void; // 值 類 declare class Person {name: string;} // 值 列舉 declare enum Color {Red, Green} // 值 名稱空間 declare namespace person {let name: string;}
我們注意到型別可以直接定義,但是值的宣告需要藉助declare關鍵字,這是因為如果不用declare關鍵字,值的宣告和初始化是一起的,如
let a: number; // 編譯為 var a;
但是編譯結果是會去掉所有的宣告語句,保留初始化的部分,而宣告檔案中的內容只是起宣告作用,因此需要通過declare來標識,這只是宣告語句,編譯時候直接去掉即可。
TypeScript也約束宣告檔案中宣告一個值必須要用declare,否則會被認為存在初始化的內容,從而報錯。
// foo.d.ts let a: number = 1; // error TS1039: Initializers are not allowed in ambient contexts.
declare也允許出現在.ts檔案中,但一般不會這麼做,.ts檔案中直接用let/const/function/class就可以宣告並初始化一個變數。並且.ts檔案編譯後也會去掉declare的語句,所以不需要declare語句。
注意,declare多個同名的變數是會衝突的
declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'. declare let foo: number; // error TS2451: Cannot redeclare block-scoped variable 'a'.
除了使用declare宣告一個值,declare還可以用來宣告一個模組和全域性的外掛,這兩種用法都是在特定場景用來給第三方包做宣告。
declare module用來給一個第三方模進行型別宣告,比如有一個第三方包foo,沒有型別宣告。我們可以在我們專案中實現一個宣告檔案來讓TypeScript可以識別模組型別:foo.d.ts
// foo.d.ts declare module 'foo' { export let size: number; }
然後我們就可以使用了:
import foo from 'foo'; console.log(foo.size);
declare module除了可以用來給一個模組宣告型別,還可以用來實現模組外掛的宣告。後面小節中會做介紹。
declare global用來給擴充套件全域性的第三方包進行宣告,後面小節介紹。
宣告檔案的模組化語法和.ts模組的類似,在一些細節上稍有不同。.ts匯出的是模組(typescript會根據匯出的模組判斷型別),.d.ts匯出的是型別的定義和宣告的值。
宣告檔案可以匯出型別,也可以匯出值的宣告
// index.d.ts // 匯出值宣告 export let a: number; // 匯出型別 export interface Person { name: string; };
宣告檔案可以引入其他的宣告檔案,甚至可以引入其他的.ts檔案(因為.ts檔案也可能匯出型別)
// Person.d.ts export default interface Person {name: string} // index.d.ts import Person from './person'; export let p: Person;
如果宣告檔案不匯出,預設是全域性可以存取的
// person.d.ts interface Person {name: string} declare let p: Person; // index.ts let p1: Person = {name: 'Sam'}; console.log(p);
如果使用模組匯出語法(ESM/CommJS/UMD),則不解析為全域性(當然UMD還是可以全域性存取)。
// ESM interface Person {name: string} export let p: Person; export default Person;
// CommonJS interface Person {name: string} declare let p: Person; export = p;
// UMD interface Person {name: string} declare let p: Person; export = p; export as namespace p;
注意:UMD包export as namespace語法只能在宣告檔案中出現。
宣告檔案中的三斜線指令,用於控制編譯過程。
三斜線指令僅可放在包含它的檔案的最頂端。
如果指定--noResove編譯選項,預編譯過程會忽略三斜線指令。
reference指令用來表明宣告檔案的依賴情況。
/// <reference path="..." />用來告訴編譯器依賴的其他宣告檔案。編譯器預處理時候會將path指定的宣告檔案加入進來。路徑是相對於檔案自身的。參照不存在的檔案或者參照自身,會報錯。
/// <reference types="node" />用來告訴編譯器它依賴node_modules/@types/node/index.d.ts。如果你的專案裡面依賴了@types中的某些宣告檔案,那麼編譯後輸出的宣告檔案中會自動加上這個指令,用以說明你的專案中的宣告檔案依賴了@types中相關的宣告檔案。
/// <reference no-default-lib="true"/>,
這涉及兩個編譯選項,--noLib,設定了這個編譯選項後,編譯器會忽略預設庫,預設庫是在安裝TypeScript時候自動引入的,這個檔案包含 JavaScript 執行時(如window)以及 DOM 中存在各種常見的環境宣告。但是如果你的專案執行環境和基於標準瀏覽器執行時環境有很大不同,可能需要排除預設庫,一旦你排除了預設的 lib.d.ts 檔案,你就可以在編譯上下文中包含一個命名相似的檔案,TypeScript 將提取該檔案進行型別檢查。
另一個編譯選項是--skipDefaultLibCheck這個選項會讓編譯器忽略包含了/// <reference no-default-lib="true"/>指令的宣告檔案。你會注意到在預設庫的頂端都會有這個三斜線指令,因此如果採用了--skipDefaultLibCheck編譯選項,也同樣會忽略預設庫。
amd-module相關指令用於控制打包到amd模組的編譯過程
///<amd-module name='NamedModule'/>這個指令用於告訴編譯器給打包為AMD的模組傳入模組名(預設情況是匿名的)
///<amd-module name='NamedModule'/> export class C { }
編譯結果為
define("NamedModule", ["require", "exports"], function (require, exports) { var C = (function () { function C() { } return C; })(); exports.C = C; });
這裡我們將自己的專案程式碼稱為“內部專案”,引入的第三方模組,包括npm引入的和script引入的,稱為“外部模組”。
自己專案中,給自己的模組寫宣告檔案,例如多個模組共用的型別,就可以寫一個宣告檔案。這種場景通常不必要,一般是某個.ts檔案匯出宣告,其他模組參照宣告。
給第三方包寫宣告檔案又分為在內部專案中給第三方包寫宣告檔案和在外部模組中給外部模組寫宣告檔案。
在內部專案中給第三方包寫宣告檔案: 如果第三方包沒有TS宣告檔案,則為了保證使用第三方包時候能夠通過型別檢查,也為了安全地使用第三方包,需要在內部專案中寫第三方包的宣告檔案。
在外部模組中給外部模組寫宣告檔案: 如果你是第三方庫的作者,無論你是否使用TypeScript開發庫,都應該提供宣告檔案以便用TypeScript開發的專案能夠更好地使用你的庫,那麼你就需要寫好你的宣告檔案。
這兩種情況的宣告檔案的語法類似,只在個別宣告語法和檔案的處理上有區別:
根據第三方包型別可以分成幾種
我們知道如果不使用模組匯出語法,宣告檔案預設的宣告都是全域性的。
declare namespace person { let name: string }
或者
interface Person { name: string; } declare let person: Person;
使用:
console.log(person.name);
如果有第三方包修改了一個全域性模組(這個第三方包是這個全域性模組的外掛),這個第三方包的宣告檔案根據全域性模組的宣告,有不同的宣告方式
如果全域性模組使用名稱空間宣告
declare namespace person { let name: string }
根據名稱空間的宣告合併原理,外掛模組可以這樣宣告
declare namespace person { // 擴充套件了age屬性 let age: number; }
如果全域性模組使用全域性變數宣告
interface Person { name: string; } declare let person: Person;
根據介面的宣告合併原理,外掛模組可以這樣宣告
interface Person { // 擴充套件了age屬性 age: number; }
上面的全域性模組的外掛模組的宣告方式可以應用於下面的場景:
如果是外掛模組的作者,希望在專案中參照全域性模組並且將擴充套件的型別輸出到宣告檔案,以便其他專案使用。可以這樣實現
// plugin/index.ts // 注意這樣宣告才會讓TypeScript將型別輸出宣告檔案 declare global { // 假設全域性模組使用全域性變數的方式宣告 interface Person { age: number } } console.log(person.age); export {};
注意,declare global寫在宣告檔案中也可以,但是要在尾部加上export {}或者其他的模組匯出語句,否則會報錯。另外declare global在宣告檔案中寫的話,編譯後不會輸出到宣告檔案中。
window的型別是interface Window {...},在預設庫中宣告,如果要擴充套件window變數(如一些hybrid環境)可以這樣實現
// window.d.ts // 宣告合併 interface Window { bridge: {log(): void} } // 或者 declare global { interface Window { bridge: {log(): void} } }
或者
// index.ts declare global { interface Window { bridge: {log(): void} } } window.bridge = {log() {}} export {};
給第三方的ESM或者CommonJS模組寫宣告檔案,使用ESM匯出或者CommonJS模組語法匯出都可以,不管第三方包是哪種模組形式。
看下面範例
interface Person { name: string; } declare let person: Person; export = person; // 也可以使用export default person;
import person from 'person'; console.log(person.name);
上面的宣告檔案是放在node_modules/@types/person/index.d.ts中,或者放在node_modules/person/package.json的types或者typings欄位指定的位置。
如果在自己專案中宣告,應該使用declare module實現
declare module 'person' { export let name: string; }
UMD模組,在CommonJS宣告的基礎上加上export as namespace ModuleName;語句即可。
看下面的ESM的例子
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通過import匯入來存取
// src/index.ts import person from 'person'; console.log(person.name);
也可以通過全域性存取
// src/index.ts // 注意如果用ESM匯出,全域性使用時候先存取defalut屬性。 console.log(person.default.name);
下面是CommonJS的例子
// node_modules/@types/person/index.d.ts interface Person { name: string; } declare let person: Person; export default person; export as namespace person;
可以通過import引入存取
// src/index.ts import person from 'person'; console.log(person.name);
也可以全域性存取
// src/index.ts console.log(person.name);
上面我們提到,declare module不僅可以用於給一個第三方模組宣告型別,還可以用來給第三方模組的外掛模組宣告型別。
// types/moment-plugin/index.d.ts // 如果moment定義為UMD,就不需要引入,直接能夠使用 import * as moment from 'moment'; declare module 'moment' { export function foo(): moment.CalendarKey; } // src/index.ts import * as moment from 'moment'; import 'moment-plugin'; moment.foo();
比如作為redux的外掛的redux-thunk的宣告檔案extend-redux.d.ts,就是這樣宣告的
// node_modules/redux-thunk/extend-redux.d.ts declare module 'redux' { // declaration code...... }
到此這篇關於TypeScript宣告檔案的語法與場景詳解的文章就介紹到這了,更多相關TS宣告檔案內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45