自從去年開(kāi)始在項(xiàng)目里寫(xiě)了一段時(shí)間 Javascript 后憎瘸,感覺(jué)沒(méi)有類型檢查的語(yǔ)言還是不太適合我顿锰,所以一直想嘗試下 TypeScript器赞,然而由于項(xiàng)目龐大垢袱,人員協(xié)作問(wèn)題,一時(shí)半會(huì)沒(méi)辦法切成 TypeScript港柜。正好最近有小程序的需求和小程序去年 11 月開(kāi)始官方支持了 TypeScript请契,所以拿來(lái)練練手。
Why TypeScript夏醉?
大概是我這半年寫(xiě)的 Swift 比較多爽锥,而 Swift 中的靜態(tài)類型和協(xié)議是我很喜歡的特性。正好 TypeScript 為 JavaScript 帶來(lái)了靜態(tài)類型和接口畔柔。
可選的靜態(tài)類型
“動(dòng)態(tài)類型一時(shí)爽氯夷,代碼重構(gòu)火葬場(chǎng)“,對(duì)于我這種極度喜歡重(xia)構(gòu)(gai)代碼的人來(lái)說(shuō)释树,JavaScript 毫無(wú)類型提示肠槽,類型全靠命名猜測(cè)是極度不友好的。而 TypeScript 加上了靈活的類型系統(tǒng)奢啥,不僅可以編碼期檢查秸仙,還能增強(qiáng)代碼的可讀性,并提供了 any 類型進(jìn)行緩沖桩盲。
接口
接口和協(xié)議寂纪,只是不一樣的叫法而已,Java、C#捞蛋、TypeScript 叫 Interface孝冒,Swift、Kotlin 叫 Protocol拟杉,就是一種規(guī)則聲明庄涡。項(xiàng)目中,和后端接口數(shù)據(jù)交互搬设,頁(yè)面?zhèn)鬟f數(shù)據(jù)穴店,數(shù)據(jù)持有,方法代理的地方拿穴,有了接口就會(huì)更加方便泣洞,易重構(gòu)。TypeScript 的 Interface + JavaScript 簡(jiǎn)單的對(duì)象就讓數(shù)據(jù)構(gòu)建變得簡(jiǎn)單又不容易出錯(cuò)默色。
小程序?qū)?TypeScript 的支持
TypeScript 有一個(gè)很重要的東西球凰,就是 d.ts 文件。d.ts 文件其實(shí)相當(dāng)于 C 系語(yǔ)言里面的 .h 頭文件腿宰,聲明了對(duì)外暴露的方法和屬性呕诉。而小程序官方對(duì) TypeScript 的支持,意味著官方會(huì)維護(hù)小程序本身 API 的 d.ts 文件吃度,也就是 typing 庫(kù)义钉,這樣當(dāng) API 發(fā)生變動(dòng)時(shí),就可以即時(shí)變更规肴。
使用也很簡(jiǎn)單,更新微信開(kāi)發(fā)者工具到最新版夜畴,在創(chuàng)建新項(xiàng)目時(shí)選擇 TypeScript 模板拖刃。
創(chuàng)建后,我們可以看到項(xiàng)目里帶上了 typings 庫(kù)贪绘,以及 TypeScript 的配置文件 tsconifg兑牡。之后,保存時(shí)就不會(huì)自動(dòng)編譯了税灌,要點(diǎn)擊小程序工具欄的編譯按鈕才可以均函。
這里有一個(gè)坑,筆者電腦安裝的 TypeScript 版本是 3.2.2 版本菱涤。編譯時(shí)會(huì)被找不到全局類型 CallableFunction 和 NewableFunction苞也。
解決方法也很簡(jiǎn)單,到 node_modules 路徑下的 TypeScript 包的 bin 目錄下粘秆,lib.es5.d.ts 文件里面把這兩個(gè)類型的 Interface 拷貝到如迟,小程序 typing 目錄下的 lib.wa.es6.d.ts 里面就可以了。小程序模板里這個(gè)文件應(yīng)該是拷貝 TypeScript 官方的,但沒(méi)有隨著官方升級(jí)而改變殷勘。
事件
視圖的事件此再,對(duì)應(yīng)的類型筆者在 typings 中并沒(méi)有看到有 Interface 定義,所以只能暫時(shí)用 any玲销,然后自己再用
as 轉(zhuǎn)一下 event 攜帶的數(shù)據(jù)的類型输拇。
Page&Data
每個(gè) Page 對(duì)象,在 typing 里是這么定義的贤斜。
declare const Page: Page.PageConstructor
interface PageConstructor {
<D extends IAnyObject, T extends IAnyObject & PageInstance>(
options: PageInstance<D, T> & T
): void
}
也就是說(shuō)策吠,它支持 D 和 T 兩個(gè)范型。這兩個(gè)范型是什么呢蠢古?小程序里奴曙,Page 是這么寫(xiě)的。
Page({});
也就是說(shuō)草讶,options 參數(shù)就是一個(gè) PageInstance洽糟,范型也被傳入了。
interface PageInstance<D extends IAnyObject = any, T extends IAnyObject = any> extends PageInstanceBaseProps<D>
PageInstance 里面定義了 Page 聲明周期的方法堕战,而且繼承自 PageInstanceBaseProps坤溃,并將范型 D 傳入。
interface PageInstanceBaseProps<D extends IAnyObject = any> {
data?: D
//...
}
所以這個(gè) D 范型嘱丢,其實(shí)就是 data 的類型接口薪介。因?yàn)?data 不是必須實(shí)現(xiàn)的,所以這里是可選型 越驻?汁政。
那么 T 是什么呢?
T extends IAnyObject & PageInstance
T 其實(shí)就是對(duì) PageInstance 的拓展缀旁,PageInstance 是 Page 的實(shí)例接口记劈,那么 T 其實(shí)就是在 Page 里面 this 的類型接口了,也就是說(shuō)并巍,需要在 Page 里新增的方法和屬性目木,都在 T 里定義。
所以懊渡,對(duì)于一個(gè)普通頁(yè)面我們可以聲明兩個(gè)接口刽射,一個(gè)代表 data, 一個(gè)代表 page剃执,舉個(gè)例子誓禁。
interface IIntroPage {
nextButtonTap(event: any): void;
isLoading: boolean;
}
interface IIntroData {
test: string;
}
Page<IIntroData, IIntroPage>({
isLoading: false,
nextButtonTap(event: any) {
this.isLoading = true;
}
});
如果這個(gè)頁(yè)面不需要 data 或者不需要擴(kuò)展 page,用 IAnyObject 代替 D 或者 T 即可忠蝗。
interface PageInstanceBaseProps<D extends IAnyObject = any> {
data?: D
setData?<K extends keyof D>(
data: D | Pick<D, K> | IAnyObject,
callback?: () => void
): void
}
同時(shí)现横,由于 setData 和 data 都被聲明為可選項(xiàng),使用時(shí)需要加上!戒祠,this.setData!({})
和 this.data!
骇两。
其他就沒(méi)什么了,用上 TypeScript 之后姜盈,官方的 API 都可以直接看參數(shù)和返回值的類型低千,再也不用去查文檔猜測(cè)類型了。
調(diào)用 JavaScript
為 JavaScript 編寫(xiě)一個(gè)簡(jiǎn)單的 .d.ts 文件馏颂,將需要調(diào)用的類和方法暴露出來(lái)示血。詳情見(jiàn)如何編寫(xiě)一個(gè)d.ts文件。
最后
雖然筆者用了 TypeScript 不久救拉,但嚴(yán)格的檢查的確讓我在增刪改接口字段能快速全局重構(gòu)难审,而且方法調(diào)用聯(lián)想,API 查看也方便了不少亿絮。小程序?qū)?TypeScript 的支持日常使用開(kāi)發(fā)是沒(méi)有什么問(wèn)題了告喊,就是官方的文檔指引比較少。