iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 28
0
自我挑戰組

Typescript 初心者手札系列 第 28

【Day 28】在 React 專案中使用 TypeScript - 宣告檔案(declaration file)

之前在探討模組解析時,不知道大家有沒有注意到 import { b } from “./moduleB”在查找模組的過程中,除了同層資料夾尋找 moduleB.ts之外,也會查找moduleB.d.ts檔案呢?

今天想來探討.d.ts究竟是什麼?.d.ts 可以稱之為宣告檔案(declaration file),d 就是 declaration 的縮寫,一般 React 專案開發不會遇到宣告檔案,是 JS 轉成 TS 編寫才會遇到的檔案格式。

宣告檔案(declaration file)

.d.ts (宣告檔案)其實就是表示主檔案.ts 型別描述的檔案。一般而言,我們會將簡單的型別定義直接寫在主檔案.ts中,遇到較複雜結構才會將特殊的型別定義放在 d.ts 中,另一種狀況則是使用第三方函式庫時。今天的文章將主要著重在第三方函式庫的宣告檔案使用。

在使用 TS 進行開發時,不可避免地有時候會需要引用第三方函式庫,有些函式庫支援 TS ,但有些仍然用純 JS 編寫,這時候如果我們想要在 TS 中使用純 JS 撰寫的函式庫,那該怎麼辦呢?

1. 第三方宣告檔案

首先,先來探討那些已經有支援型別的函式庫。對於那些已經支援 TS 的函式庫,我們只需要查找別人已經幫我定義好的第三方宣告檔案即可。在 TS 2.0 版本之後,進行查找官方推薦使用 npm 來管理,舉例來說:要取得 lodash 和 jQuery 函式庫的宣告檔案,只需要打下面的指令,使用 npm 安裝即可

npm install @types/lodash --save
npm install @types/jquery --save

一旦 npm 下載完,就可以在全域或模組使用。宣告檔案將被安裝到node_modules@types\lodash 及 node_modules@types\jQuery 資料夾中。

模組使用
import * as _ from "lodash";
_.padStart("Hello TypeScript!", 20, " ");
全域使用
_.padStart("Hello TypeScript!", 20, " ");

在大多數情況下,型別宣告檔案會和第三方函式庫名稱相同,但加上前綴@types/。如果需要,也可以在利用此頁面搜尋需要的宣告檔案。

如果使用的第三方函式庫沒有已經定義好的宣告檔案呢?那就只能自力救濟自己寫了!

tsconfig.json檔設定

如果希望較好的管理第三方函式庫宣告檔案的引用,不希望允許自動全域調用,則可以在 tsconfig.json 中的 typeRoots 和 types 中設定。

{
    "compilerOptions": {
        "typeRoots": [],
        "types" : [
            "jquery"
        ]
    }
}

typeRoots 是用來設定 TS 編譯器查找宣告檔案的位置,預設為 ./node_modules/@types,由於上面僅在 types 中放入 jQuery ,因此即使使用npm install @types/node安裝了另一個第三方函式庫如 Node.js,也不能在全域使用,除非將 Node.js 加到types中。

2. 自行編寫宣告檔案

一般來說,撰寫宣告檔案的方式取決於函式庫如何使用,需要從函式庫本身的原始碼進行分析,大致可以分成幾種:

  1. 全域函式庫(Global Libraries)、全域套件(Global Plugin)、可全域修改的模組(Global-modifying Modules): 能在全域命名空間下使用的(例如:不需要使用 import)
  2. 模組函式庫(Modular Libraries):部分函式庫只能在模組加載器的環境下使用,例如express只能在Node.js環境下使用,必須使用 CommonJS 的 require 函式
  3. UMD 模組 : 可以作為模組導入,也可以全域(沒有模組加載器的環境)使用的模組

1.1 全域函式庫、套件

目前大部分常見的全域函式庫都以下面的UMD函式庫形式編寫

辨別方式

會看到:

  • 全域的var語句或function宣告
  • 一個或多個window.someName賦值語句
  • 假設原始 DOM 像 document 或 window 是存在的

不會看到:

  • 檢查是否使用模組加載器,例如:require 或 define
  • CommonJS/Node.js的導入如:var fs = require("fs");
  • define(...)

舉例來說可能會見到:

function createGreeting(s) {
    return "Hello, " + s;
}

window.createGreeting = function(s) {
    return "Hello, " + s;
}

1.2 全域可修改的模組

如何識別

一般來說跟上面的全域函式庫或全域套件類似,但是需要require來啟動效果。


var unused = require("magic-string-time");
//或
require("magic-string-time");

var x = "hello, world";
console.log(x.startsWithHello());

var y = [1, 2, 3];
console.log(y.reverseAndSort());

2. 模組函式庫

目前許多常見的函式庫如express、gulp和request都是模組函式庫。

如何識別

會看到:

  • 使用 require 或 define
  • 像 import * as a from 'b' 或 export c 這樣的宣告
  • 賦值给 exports 或 module.exports

很少看到:

  • 對 window 或 global的賦值

舉例來說可能會見到:

var fs = require("fs");
//或
import fs = require("fs");
//或
var someLib = require('someLib');
//或
define(..., ['someLib'], function(someLib) {
 ...
});

3. UMD 模組

可以作為模組使用,也可以作為全域使用,常見的函式庫例如:Moment.js 、jQuery、lodash 就屬於 UMD 模組。

如何識別

UMD模組會檢查是否存在模組加載器環境,舉例來說:

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["libName"], factory);
    } else if (typeof module === "object" && module.exports) {
        module.exports = factory(require("libName"));
    } else {
        root.returnExports = factory(root.libName);
    }
}(this, function (b) {

如果看見像上面的 typeof define、typeof window 或 typeof module 這樣的檢查語法,特別是在檔案的上方,那麼極高可能是UMD函式庫。

在官網中針對上面各種不同類型的函式庫也提供了不同的模板供開發者撰寫宣告檔案的參考。

相依性

倘若使用的函式庫有相依情況,則需要將相依檔案導入宣告檔案,引入後使用.運算子進行調用

1. 相依全域函式庫

使用/// <reference types="..." />導入

/// <reference types="someLib" />
function getThing(): someLib.thing;

2. 相依模組

使用 import 導入

import * as moment from "moment";
function getThing(): moment;

3. 相依UMD模組

如果是全域函式庫相依UMD模組,則使用/// <reference types

/// <reference types="moment" />
function getThing(): moment;

如果是模組或UMD相依UMD,則使用import

import * as someLib from 'someLib';

注意:不要使用/// <reference去宣告UMD模組的相依性。

小結

今天主要探討了使用宣告檔案的使用情境,介紹了如何載入已定義好的宣告檔案,以及如何判斷函式庫使用方式,以自行撰寫宣告檔案。至於,如何自行撰寫宣告檔案以及發布,則留待明天繼續探討。


上一篇
【Day 27】在 React 專案中使用 TypeScript - 命名空間(namespace)
下一篇
【Day 29】在 React 專案中使用 TypeScript - 宣告檔案(declaration file)(下)
系列文
Typescript 初心者手札30

尚未有邦友留言

立即登入留言