TypeScript,初次見面,請多指教

TypeScript,初次見面,請多指教

為什麼用TS ?

說實話,最開始並沒有想把TS 用到實際項目中來,一來是感覺“類型”會限制JS 的優勢(好吧,就是浪寫浪慣了);二來聽聞TS + Redux 的酸爽滋味,有點望而卻步;三來TS 環境使用的庫需要加類型的聲明,很多庫並不支持,有點擔心推進的流暢度...

這個時候,就需要有一股無形的力量推你一把。推我的是團隊正在日益普及TS, 我希望推動你的可以是這篇文章~

接下來,會有React + TS 的項目為背景,介紹我在初學TS 開發項目中遇到的一些問題,希望對你有所幫助。

初學者的困惑

一.如何優雅的聲明類型

  1. 基礎

不就是比JS 多了一個類型聲明嗎?老夫擼起袖子拎起鍵盤就是一梭子:

interfaceBasic{num:number;str:string|null;bol?:boolean;}

輕輕鬆松,五種JS 值類型就聲明好了。那數組、函數呢?再來:

interfaceFunc{func(str:string):void;}interfaceArr{str:string[];mixed:Array<string|number>;fixedStructure:[string,number];basics:Basic[];}

除此之外,竟然還可以定義自己的類型呢,比如常用的回調函數,在聲明處需要指定回調函數的類型:

event.on('change',function(){});

那這個on方法需要如何聲明呢?試試看Function當cb函數的類型呢

on(type:string,cb:Function):{}

然後就恭喜了,你會得到一個tslint error :

慶幸的是,在這個error 裡面它告訴了你應該怎麼做:聲明一個專用的函數類型就可以了:

type Cb = () => void; on(type: string, cb: Cb);

至此,我們的TS 人生算是起步了

另外,枚舉類型也是很常用的,比如聲明一個狀態機的各個狀態:

enumStatus{Draft,Published}// 也可指定值
enumStatus{Draft='Draft',Published='Published'}

在使用枚舉的時候,常會遇到如何將枚舉和原始數據類型相互轉換的需求,比如接口請求到的status是Draft字符串,但是代碼中聲明的status是Enum類型,如何轉換呢?

// string to enum const str = 'Draft'; const status: Status = Status[str]; // enum to string Status[Status.Published] === 'Published'

2.糅合

獨立的類型或接口聲明看起來似乎並沒有那麼難,到項目中糅合一下呢?

  • 可能會有幾十個類型聲明;
  • 類型聲明可能出現在接口入參出參中、 React組件的Props和State中、函數方法中;
  • 當項目到達一定規模,可以抽像出獨立的庫的時候,類型也需要抽象;
  • ...

你可能遇到各種情況,會打破你對TS 的掌控。如何是好?

先說我們實踐下來的結論:獨立聲明就近聲明按職責分組杜絕“硬湊”關聯有限抽象。

-獨立聲明

一個ts 文件只聲明一個類型或者接口,文件名為需要暴露的類型名稱,方便檢索和管理。

-就近聲明

當一個聲明沒有被外部引用或者依賴時,可以考慮就近放在使用的地方,典型的場景是React 組件的Props 和State 的類型聲明。

-按職責分組

在項目中,需要聲明類型的可大致分為兩類:一類是model,也就是接口請求相關的,包括入參和出參;另一類是view,界面渲染相關的。因此,我在獨立聲明的基礎上,可以類型按照model和view的維度進行分組,相互獨立。

那麼問題來了,如果是獨立的類型聲明的話,怎麼把model 的數據應用到view 呢?可能你需要一個adapter來做類型的的轉換:DTOTypes -> adapter -> ViewTypes,完成類似於將接口中的字符串映射成枚舉類型這之類的轉換。

-杜絕“硬湊”關聯

不要硬湊兩個接口或者類型的關係,比如一個接口的創建和更新,可能字段都是一樣,區別是一個有id 另一個沒有,於是我們可能就想著寫一個類型然後id 可選就好了。這樣是少寫了一個類型,但是可能會帶來另外一些麻煩,比如帶id 的數據傳給了新建的接口,但是ts 檢查不出來。所以,建議不要怕麻煩,直接拆分成CreateInputDTOUpdateInputDTO .

-有限抽象

杜絕“硬湊”關聯的基礎上,我們可以抽像出通用的聲明。

基於上述原則,解決了我作為一個初學者在類型聲明上的困擾,如有不對的或者更好的建議,歡迎指正~

3.萬能藥膏any

不是所有的類型聲明都能一馬平川的,當遇到確實解決不了的類型報錯的時候, as any能帶給你不一樣的快感,但是不建議使用啊...

二.如何引用外部庫

接下來聊聊第三方庫在TS 環境下的使用。

在JS 中,npm 上有豐富的海量的庫幫我們完成日常的編碼,可能並不是所有的庫都能完全被應用到TS 中,因為有些缺少類型聲明。

比如,在TS中使用react ,你會得到這樣的一個類型檢查錯誤:

因為react 的庫中並沒有類型聲明。

現在比較通用的做法是,能力實現和類型實現獨立成兩個庫,也就是你需要再安裝類型聲明的庫: @types/react .

當遇到上述問題的時候,嘗試安裝一下@types/[package] .

然而,並不是所有的庫都有類型聲明的實現,也會有很多不支持TS 的存在,然而又必須得使用這個庫的時候該怎麼辦?

自己寫聲明!

progressbar.js為例,基本使用方法是:

import*asProgressBarfrom'progressbar.js';newProgressBar.Circle(this.$progress,{strokeWidth:8,trailColor:'#e5e4e5',trailWidth:8,easing:'easeInOut'});

我們需要對庫中暴露出的api 去做聲明,對上述例子做個分解:暴露了Circle 類,Circle 構造函數包含兩個參數,一個HTMLElement,一個options. OK, come on~

// 首先声明一下模块: declare module 'progressbar.js' { // 模块中暴露了Circle 类export class Circle { constructor ( container : HTMLElement , options : Options ); } // 构造函数的Options 需要单独声明interface Options { easing ?: string ; strokeWidth ?: number ; trailColor ?: string ; trailWidth ?: number ; } }

如此我們便完成了一個簡單的聲明,當然實際使用中的API 肯定比上述情況復雜,根據使用情況,用了哪些API 或者參數,就補充那些的聲明即可。

三.如何組織一個TS項目

TS 項目的目錄組織上,跟JS 項目一樣,補充好types 的聲明就可以了。

需要注意的是,將你希望對外暴露的能力相關的類型聲明都暴露出去,不友好的聲明會讓接入你項目的人非常的痛苦,同時,在package.json 中需要指定type 的path, 比如: "types": "dist/types/index.d.ts"

另外,務必加上tslint ,更規範的去用TS實現功能,對於入門而言尤為重要。

TS 帶來的改變

接觸TS 一個月的感受上來說,過了磨合期的痛苦,就能慢慢感受到TS 帶來的便利。

比如,有一個類型你記得名字是ABC,你在VSCode 中輸入A,然後發現,竟然能找到我的聲明,按一下回車,臥槽,自動給你import 進來了,不用在一個個字的輸入../../../../,不用算目錄層級是否正確了,是不是很爽。

另外,強類型並不是沒有好處啊,浪寫慣了可能還是會留隱患的,有點約束也好...

雖然你每天要多敲很多import * as xx from 'xx' ,但是你的代碼也更為可靠了不是。

與君共勉,提前感受下一代ES 標準,TS 用起來吧~


坐標南京阿里巴巴,求前端P7 一枚,崗位要求和待遇同杭州,技術棧限React,有Typescript 經驗者更佳。有意者聯繫:henry.lx@alibaba-inc.com

在React 中處理數據流問題的一些思考

CSS愛好者們,第5屆CSS大會來啦!