為jQuery Plugin撰寫TypeScript定義檔

TypeScript是強型別的世界,透過預先宣告物件、屬性、方法、介面,在編輯階段提供Intellisense提示、Go Definition、Find All References、Rename... 等編譯式語言才有的功能,而編譯時可預先抓出參數、型別、方法錯誤,降低執行階段發現修復的高昂成本。

開始使用TypeScript一段時間後,一定會發現一項困擾: 雖然DefinitelyTyped計劃已匯集許多常用JavaScript程式庫的TypeScript定義檔,但畢竟無法涵蓋你會用到的每一個JavaScript程式庫,以我自己為例,馬上面對的問題的便是: 我找不到jQuery BlockUI的定義檔。

於是,在程式中要引用block(),blockUI()會出現紅蚯蚓無法編譯。

有一個取巧解法,只要不在全域範圍,在module、interface、class內部可以透過declare指令重新將jQuery、$定義成any型別,declare的變數並不會出現在JavaScript中,純粹讓編譯器假設該變數存在。如此jQuery變成任意型別物件,不管加上什麼屬性、呼叫任何方法都視為合法,但這得付出代價 – 與jQuery相關的Intellisense與編輯檢查從此失效,退回JavaScript時代。

我曾想到一個投機做法,declare var $就好,當需要強型別時寫jQuery("div"),需要用無定義檔方法時寫$("div")。不過身為程式魔人,一直偷雞摸狗下去也不是辦法,還是乖乖學會怎麼為jQuery Plugin寫定義檔吧!

這裡先試寫一個簡單但無聊的jQuery Plugin當成練習目標,為了涵蓋常遇到的各式情境,我刻意加入多種存取API:

$("…").fill(); 元素塗色(用預設顏色)
$("...").fill({ color: "red" }); 元素塗色(指定顏色)
$.fill(); 網頁塗色(用預設顏色)
$.fill({ color: "red" }); 網頁塗色(指定顏色)
$.fill.options.color = "blue"; 修改預設顏色
$.title(); 取得網頁標題
$.title("…"); 設定網頁標題

TypeScript程式碼如下:

/** jquery.fill options */
interface JQFillOptions {
    /** fill color */
    color?: string;
}
 
(function ($) {
    var defaultOptions: JQFillOptions = {
        color: "red"
    };
    //merge option and default option to get fill color
    function getColor(options?: JQFillOptions) {
        return $.extend({}, defaultOptions, options).color;
    }
    //fill background to document.body
    $.fill = function (options?: JQFillOptions) {
        $("body").css("background-color", getColor(options));
    }
    //global options
    $.fill.options = defaultOptions;
    //fill background for element
    $.fn.fill = function (options?: JQFillOptions) {
        return this.each(function () {
            $(this).css("background-color", getColor(options));
        });
    };
    //get and set document.title
    $.title = function (title?: string) {
        if (title)
            document.title = title;
        else
            return document.title;
    };
})(jQuery);

寫個網頁測試:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>jQuery Fill Plugin</title>
    <style>
        div { 
            margin: 12px; padding: 6px; width: 100px; 
            color: white;
        }
    </style>
</head>
<body>
    <div>Test</div>
    <div>Test</div>
    <script src="../Scripts/jquery-2.1.1.js"></script>
    <script src="jquery.fill.js"></script>
    <script>
        $.fill.options.color = "yellow";
        $.fill();
        $("div").first().fill({ color: "blue" })
        .end().last().fill({ color: "green" });
        $.title($.title() + "$$");
    </script>
 
</body>
</html>

測試成功!

接著我們把JavaScript抽出來改寫為TypeScript,一如預料,馬上遇到JQuery、JQueryStatic未定義fill、title的錯誤,無法編譯。

為了讓TypeScript認識我們的Plugin,我們需在jquery.fill.ts加入interface JQuery及interface JQueryStatic宣告,TypeScript會將它們合併到JQuery定義檔的同名interface中,如此JQuery會多了.fill()以支援$("…").fill()語法、JQueryStatic會增加.fill()、.title()以支援$.fill()、$.title()。不過有個地方比較麻煩,由於要同時支援$.fill()、$.fill.options兩種存取方式,需多宣告一個interface JQFillStatic,其中包含一個方法(options?: JQFillOptions)以及一個屬性options,而JQueryStatic interface的fill型別為JQFillStatic,如此才能讓$.fill()與$.fill.options都有效。宣告程式如下:

/** jquery.fill options */
interface JQFillOptions {
    /** fill color */
    color?: string;
}
interface JQuery {
    /**
     * 將元素填滿背景色
     * 
     * @param options 顏色設定,未提供時依預設值
     */
    fill(options?: JQFillOptions): JQuery;
}
interface JQFillStatic {
    /**
     * 將document.body填滿背景色
     * 
     * @param options 顏色設定,未提供時依預設值
     */
    (options?: JQFillOptions);
    /** 顏色預設值 */
    options?: JQFillOptions;
}
interface JQueryStatic {
    fill?: JQFillStatic;
    /** 
     * 取得或設定document.title
     * @param title 要設定的網頁標題,未提供時傳回現有標題
     */
    title(title?: string);
}
 
(function ($) {
    ///...省略...
})(jQuery);
 

如此,TypeScript就認得我們的Plugin囉~

在以上案例,JQuery Plugin用TypeScript開發,因此JQuery、JQueryStatic宣告直接寫入同一TypeScript檔即可。如果是第三方JavaScript,做法則比照scripts/typings/jquery/jquery.d.ts,要為該JavaScript檔寫一個同檔名的.d.ts。更進一步,還可將你寫好的定義檔貢獻到DefinitelyTyped,分享給開發社群,這才是新時代的好男兒! (遠目)

劍及履及,我的第一個TypeScript定義檔已經被Merge到DefintelyTyped,DefinitelyTyped會自動將其包成NuGet Package,所以jQuery BlockUI現在有TypeScript定義檔囉~

關於撰寫定義檔,TypeScript CodePlex上有篇教學: Writing Definition (.d.ts) Files,如果你也有心參與DefinitelyTyped的定義檔補完計劃,可以參考貢獻指南

歡迎推文分享:
Published 03 July 2014 10:24 PM 由 Jeffrey
Filed under:
Views: 15,177



意見

# Jerry said on 17 June, 2016 04:42 AM

不好意思,

我想請問。有的plugin使用方法是

$.fn.pluginname.fun();

我根據提供的API文件寫定義檔

但是不曉得怎麼定義 $.fn.pluginname

這應該是個物件

目前是這樣做的:

interface JQuery {

   pluginname: {

       ...

       fun: () => void;

   }

}

但是在 visual studio 上寫測試的typescript時,

autocomplete(是這樣說嗎?)的選單裡不是我註解的jsdoc內容

雖然沒有錯誤,但是這樣沒有type checking了吧?

請問我是否漏掉了甚麼?

謝謝!

# Jeffrey said on 18 June, 2016 06:42 PM

to Jerry, 請參考我的測試

# Jerry said on 20 June, 2016 04:52 AM

有耶。

謝謝!

我了解一下jQuery.fn

它是jQuery.prototype的alias

建立基於jQuery的plugin,透過擴增fn,即增加新的成員函式

所以jQuery 物件都可使用到

如:

jQuery([selector]).pluginname.fun();

#([selector]).pluginname.fun();

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<July 2014>
SunMonTueWedThuFriSat
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication