0%

TypeScript - declare 在幹嘛

Why ?

如果我們想要在舊的專案引入 typeScript,
常常會礙於原本我們自己撰寫的 function 或者第三方套件的 api 都是用純 JS 的程式碼做撰寫,
無法直接將 typeScript 的型別注釋加在這些檔案裡面。
導致我們在調用以上這些 function 或者 api 的時候,不會具有 TS 的型別檢驗的效果

1
2
3
4
5
6
7
function isSmaller(arg, base) { // 預期 arg 和 base 這兩個參數都只能傳入 number 型別的值
return arg > base;
}

const result = isSmaller(10, 100);
const failedType = isSmaller(10, true); // ❌ 因為沒有 isSmaller 這個 function 的型別註釋,所以,在這邊調用的時候,不會有傳入型別錯誤的提示
console.log(result);

但透過 declare 這個語法針對在專案裡我們調用的方法來撰寫屬於它們的型別註釋的話,就可以在使用這些功能時,具有 TypeScript 的型別檢查的效果囉。

How

為了在調用專案裡的功能時具有 TS 的型別檢查效果,我們需要為它們製作 .d.ts 檔案,並在這些 .d.ts 檔案裡面使用 declare 語法為我們調用的功能製作屬於它們的型別定義。
這邊就舉個小小範例

預計的資料夾結構如下

1
2
3
4
5
6
utils
|- compare
|- isSmaller.js
|- isSmaller.d.ts
main.js
tsconfig.json

step 1.
輸入 npm i typescript 在專案裡面安裝 TypeScript。
並創建 compare 資料夾,並在這個資料夾裡創建 isSmaller.js 和 isSmaller.d.ts 這兩個檔案。
isSmaller.js 檔案裡,撰寫以下內容:

1
2
3
4
--- /compare/isSmaller.js ---
export function isSmaller(arg, base) {
return arg < base;
}

接著,你先不要在 isSmaller.d.ts 檔案裡面寫任何內容。
你先把 tsconfig.json 的設定內容寫成下面這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--- tsconfig.json ---
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "ES2016",
"checkJs": true,
"noImplicitAny": true,
"alwaysStrict": true,
},
"include": [
"./**/*"
],
"exclude": [
"./built",
"node_modules"
]
}

上面的 tsconfig.json 檔案裡的設定中最主要就是在 "include" 這個屬性設定 TS 要 檢查哪些路徑底下的檔案。
經過以上的設定,
你應該會發現 /compare/isSmaller.js 定義的 isSmaller 這個 function 的參數已經有 TS 回報 Parameter 'arg' implicitly has an 'any' type. 的錯誤,
會有這個錯誤的產生是因為在 tsconfig.json"noImplicitAny": true 這個設定所造成的,
由此可看出 TS 已經有檢查我們在 include 設定的路徑底下的檔案型別。

step 2.
compare 資料夾裡的 isSmaller.d.ts 檔案加入以下的型別

1
2
--- isSmaller.d.ts ---
export declare function isSmaller(arg: number, base: number): boolean;

step 3.
main.js 引入 isSmaller .js 檔案裡的 isSmaller function
接著,寫入以下兩行

1
2
3
4
--- main.js ---
// ...
isSmaller(10, 100);
isSmaller(10, true); // ❌ Argument of type 'boolean' is not assignable to parameter of type 'number'.

你應該可以看到上面那一行不會報錯,但是,下面那一行會報錯,
因為,第二行程式碼傳入的參數並不是 number 的型別,所以,才會報錯。
由此,也可以看出來,我們在 isSmaller.d.ts 的型別定義的檢查功能確實會在我們調用 isSmaller 這個 function 被觸發。

So What ?

可以看出來透過 declare 的功能可以將 型別定義的內容 與 既有的 JS 程式碼 寫在不一樣的檔案中。
並在專案中使用那些有透過 declare 定義它們的型別定義的功能時,觸發 TS 的型別檢驗功能。
用這種方式就可以漸漸地讓 TS 慢慢地覆蓋舊有專案,最終讓整個專案都有 TS 的型別檢驗的保護。
所以,在某些專案的 package.json 裡,可以看到以 @type/ 開頭的檔案(e.g. @type/lodash),這種檔案就是這些 library 自己的 Typescript 型別定義內容。

Source Code

https://github.com/Landy510/ts-declaration-practice

https://stackoverflow.com/questions/61932377/how-to-use-typescript-declaration-files-alongside-javascript

https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html