不能否認,現在 TypeScript 已成爲一個前端工程師的所須要具有的基本技能。嚴謹的類型檢測,一方面是提升了程序的可維護性和健壯性,另外一方面也在潛移默化地提升咱們的編程思惟,即邏輯性。javascript
那麼,今天我將會經過結合實際開發場景和 Vue 3.0 源碼中的部分類型定義來簡單聊聊 TypeScript 中的高級類型。前端
interface
被用於對全部具備結構的數據進行類型檢測。例如,
在實際開發當中,咱們會定義一些對象或數組來描述一些視圖結構。vue
定義對象java
常見的有對錶格的列的定義:ios
const columns = [ {key: "username", title: "用戶名"}, {key: "age", title: "年齡"}, {key: "gender", title: "性別"} ]
而這個時候,咱們就能夠經過定義一個名爲 column
的接口:typescript
interface column { key: string, title: string } // 使用 const columns: column[] = [ {key: "username", title: "用戶名"}, {key: "age", title: "年齡"}, {key: "gender", title: "性別"} ]
定義函數編程
咱們日常開發中使用的 axios
,它的調用方式會有不少種,例如 axios.request()
、axios.get()
、axios.put()
。它本質上是定義了這麼一個接口來約束 axios
具有這些方法,它看起來會是這樣:axios
export interface Axios { request(config: AxiosRequestConfig): AxiosPromise get(url: string, config?: AxiosRequestConfig): AxiosPromise delete(url: string, config?: AxiosRequestConfig): AxiosPromise head(url: string, config?: AxiosRequestConfig): AxiosPromise options(url: string, config?: AxiosRequestConfig): AxiosPromise post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise }
可複用性是咱們日常在寫程序時須要常常思考的地方,例如組件的封裝、工具函數的提取、函數設計模式的使用等等。而對於接口也一樣如此,它能夠經過繼承來複用一些已經定義好的 interface
或 class
。設計模式
這裏咱們以 Vue 3.0 爲例,它在 compiler
階段,將 AST Element
節點 Node
分爲了多種 Node
,例如 ForNode
、IFNode
、IfBranchNode
等等。而這些特殊的 Node
都是繼承了 Node
:數組
Node
的 inteface
接口定義:
export interface Node { type: NodeTypes loc: SourceLocation }
IFNode
的 interface
接口定義:
export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] codegenNode?: IfConditionalExpression }
能夠看到 Node
的接口定義是很是純淨的,它描述了 Node
所須要具有最基本的屬性:type
節點類型、loc
節點在 template
中的起始位置信息。而 IFNode
則是在 Node
的基礎上擴展了 branches
和 codegenNode
屬性,以及重寫了 Node
的 type
屬性爲 NodeTypes.IF
。
這裏簡單介紹一下 IFNode
的兩個屬性:
branches
表示它對應的 else
或 else if
的節點,它多是一個或多個,因此它是一個數組。codegenNode
則是 Vue 3.0 的 AST Element
的一大特色,它描述了該節點的一些屬性,例如 isBlock
、patchFlag
、dynamicProps
等等,這些會和 runtime
的時候靶向更新和靶向更新密切相關。近段時間,我也在寫一篇關於 Vue 3.0 如何實現runtime
+compile
優雅地實現靶向更新和靜態提高的文章,應該會在下週末竣工。
對於 interface
的介紹和使用,咱們這裏點到即止。固然,它還有不少高級的使用例如結合泛型、定義函數類型等等。有興趣的同窗能夠自行去了解這方面的實戰。
交叉類型故名思意,有着交叉之效。咱們能夠經過交叉類型來實現多個類型的合併。例如,在 Vue3 中,compile
階段除了會進行 baseParse
以外,還會進行 transform
,這樣最後的 AST
纔會進行 generate
生成可執行的代碼。因此,compile
階段它就會對應多個 options
,即它也是一個交叉類型:
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
而這個 CompilerOptions
類型別名會用於 baseCompiler
階段:
export function baseCompile( template: string | RootNode, options: CompilerOptions = {} ): CodegenResult {}
而爲何是多個options
的交叉,是由於baseCompiler
只是最基礎的compiler
,上面還有更高級的compiler-dom
、compiler-ssr
等等。
一樣地,聯合類型其有着合的效果。即有時候,你但願一個變量的類型是 String
或者是 number
的時候,你就可使用聯合類型來約束這個變量。例如:
let numberOrString: number | string numberOrString = 1 numberOrString = '1'
而且,在實際的開發中使用聯合類型,咱們一般會遇到這樣的提示,例如:
interface student { name: string age: number } interface teacher { name: string age: number class: string } let person: student | tearcher = {} as any person.class = "信息161" // property 'person' does not exit on type `student`
這個時候,咱們能夠利用類型斷言,告訴 TypeScript 咱們知道 person
是什麼:
(person as teacher).class = "信息161"
對於交叉類型和聯合類型,應該是屬於咱們日常開發中會使用頻繁的部分。而且,從它們的概念上,不難理解,它們本質上就是數學中的並集和交集,只不過在基本類型上有着不一樣的表現而已。
在講聯合類型的時候,咱們說了它本質上是交集,這也致使了咱們不能直接使用交集以外的屬性或方法。因此,咱們得經過在不一樣狀況下使用類型斷言來告知 TypeScript 它是什麼,從而使用交集以外的屬性。
可是,頻繁地使用類型斷言無疑下降了代碼的可讀性。而針對這一問題,TypeScript 提供了類型保護的機制來減小類型斷言,它是一個主謂賓句,例如仍然是上面的例子,咱們能夠實現這樣的類型保護:
function isTeacher(person: Tearcher | Student): person is Teacher { return (<Tearcher>person).class !== undefined }
而後經過 isTeacher
這個函數來判斷當前類型,再進行相應地屬性訪問:
if (isTeacher(person)) { // 這裏訪問 teacher 獨有的屬性 person.class = "信息162" } else { person.name = "wjc" }
有了類型保護,這個時候咱們就會遇到這樣的問題:若是個人聯合類型中存在多個類型,那麼豈不是得定義多個相似 isTeacher
這樣的藉助類型保護的函數?幸運的是,在 TypeScript 使用 typeof
或 instanceof
能夠自動實現類型保護和區分。
在 TypeScript 中對基礎類型使用 typeof
時,會自動進行類型保護,例如:
function isNumberOrString(param: string | number) { if (typeof param === 'string') { return param.split('') } else { return param++ } }
不一樣於 typeof
只能對基礎類型進行類型保護,instanceof
能夠實現對全部類型的類型保護。它是經過構造函數來實現類型保護。
interface Person { name: string age: string } class Teacher implements Person { name: string age: string constructor(name: string, age: string) { this.name = name this.age = age } teach() { console.log("i am a teacher.") } } class Student implements Person { name: string age: string constructor(name: string, age: string) { this.name = name this.age = age } study() { console.log("i am a student.") } } const person: Student | Teacher = null as any if (person instanceof Teacher) { person.teach() } else { person.study() }
對於 typeof
或 instanceof
其實在 JavaScript 中也是老生常談的知識點,由於傳統的類型檢測咱們會更偏向使用 Object.String.prototype.toString()
來實現。可是,反而在 TypeScript 中,它們二者反而混的如魚得水。
類型別名,我想你們腦海中第一時間想到的就是 Webpack 中的 alias
爲路徑配置別名。可是,TypeScript 中的類型別名與它只是形同,可是意不一樣。通俗點講,就是經過它能夠給類型起一個名稱:
type age = number const getAge = () => age // 等同於 interface Age { getAge():number }
字面量類型是指咱們能夠經過使用類型別名和聯合類型來實現枚舉類型,例如:
type method = get | GET | post | POST | PUT | put | DELETE | delete
對於索引類型和映射類型,這裏咱們經過 loadash
中經常使用的一個函數 pluck
來說解:
在 JavaScript 中實現:
function pluck(object, names) { return names.map(name => object[name]) }
在 TypeScript 中實現:
function puck<T, K extends keyof T>(object: T, names: K[]): T[K][] { return names.map(name => object[name]) }
這裏咱們爲 puck
函數定義了兩個泛型變量 T
和 K
,其中 K
是繼承於 T
中全部屬性名的類型,因此形參中 names
被約束爲 T
中屬性的數組,這個過程被稱爲類型索引。而對於 puck
函數的返回值 T[K][]
,則表明返回的值是一個數組,而且數組值被約束爲 T
中屬性值爲 K
的值,這個過程被稱爲索引訪問。
理解這兩種概念可能會有點晦澀,可是對於每一者都分開去理解過程會比較有邏輯性。
雖然,TypeScript 已成爲一項前端工程師的必備技能。可是,相信不少小夥伴仍是用的 Javascript 比較多。因此,可能會存在困擾,我該如何提升 TypeScript 編程能力?其實,這個問題很簡單,開源的時代,今天咱們不少問題均可以經過閱讀一些開源的項目源碼來解決。這裏,我推薦你們能夠嘗試着去閱讀 Vue3.0 的源碼,相信經過閱讀,你的 TypeScript 編程能力會有質的飛躍。
寫做不易,若是你以爲有收穫的話,能夠帥氣三連擊!!!
參考資料:
)
往期文章回顧