您好,登錄后才能下訂單哦!
這篇文章主要介紹“TypeScript如何自定義數據類型”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“TypeScript如何自定義數據類型”文章能幫助大家解決問題。
TypeScript 在 JavaScript 的基礎上增加了靜態類型系統,它使代碼的可讀性更強,讓代碼重構變得更容易。但是對 TypeScript 而言,它的靜態類型系統是可選的,這讓JavaScript 程序很容易就能遷移到 TypeScript 程序。
類型系統是一組規則,它用來規定編程語言如何將變量、類、函數等識別為不同的類型,如何操作這些類型以及不同類型之間的關系。類型系統分為靜態類型系統和動態類型系統。
動態類型系統
JavaScript 是一種動態類型的編程語言,它在運行階段進行類型檢查,所以與類型相關的錯誤要在運行階段才會被暴露出來。
靜態類型系統
TypeScript 在 JavaScript 的基礎上增加了靜態類型系統,它使 TypeScript 程序在編譯階段就進行類型檢查,與類型相關的錯誤在編譯階段就能暴露出來,這使開發人員能提前發現類型錯誤。
TypeScript 的類型系統是一個結構化類型系統,在結構化類型系統中,如果兩個類型有相同的結構,不論它們的類型名是否相同,則認為它們是相同類型。這意味著類型名不重要,只要結構是匹配的,類型就兼容。
在 TypeScript 中有多種方式去描述函數的簽名,例如:函數類型表達式、接口類型。在這里先介紹如何用函數類型表達式描述函數的簽名。函數類型表達式語法如下:
// 這是一個函數類型,它描述的函數接受兩個參數,分別是name和age,name是string類型,age是number類型,這個函數沒有返回值 (name: string, age: number) => void // lineA // 這是一個函數類型,它描述的函數接受一個參數,這個參數是number類型,函數的返回值的類型是 number (a: number) => number
函數類型表達式語法與 ES2015 的箭頭函數語法很相似,但是函數類型表達式不會創建任何函數,它只存在于 TypeScript 編譯時。從上述代碼可以看出,函數的返回值類型放在箭頭符號(=>)的后面,函數的參數類型以 :type 的形式放在參數的后面。代碼清單1演示了如何使用函數類型表達式。
代碼清單1
// 聲明一個名為 startHandle 的變量,它的數據類型是函數,它沒有返回值,它接受一個名為fn的參數,并且 fn 的數據類型也是函數 let startHandle: (fn: (a: number, b: number) => void) => void // line A // 在這里將一個箭頭函數賦值給 startHandle startHandle = (fn: (a: number, b: number) => void) => { // line B if (Math.random() < 0.5) { fn(1,2) } else { fn(3,4) } } function printResult(val1: number,val2: number): void { console.log(val1 + val2) } startHandle(printResult)
代碼清單1中的 line A 和 line B 乍一看不好理解,主要是它太長了,而且存在冗余的部分,可以使用類型別名解決這個問題。
定義類型別名需要用到的關鍵字是 type,用法如下:
type myFnType = (a: number, b: number) => void
接下來就能在代碼中用 myFnType 代替 (a: number, b: number) => void,讓代碼更加的簡潔。修改代碼清單1中的代碼,得到代碼清單2。
代碼清單2
type myFnType = (a: number, b: number) => void let startHandle: (fn: myFnType) => void // line A startHandle = (fn: myFnType) => { // line B if (Math.random() < 0.5) { fn(1,2) } else { fn(3,4) } }
修改之后,代碼清單2中的 line A 和line B 比代碼清單1中的 line A 和 line B 簡潔很多,而且也更加容易理解。
代碼清單1和代碼清單1中的函數類型,它們每一個參數都是必填的,但在某些情況下,我們要讓函數參數是可選的,在函數參數的類型注釋的前面加一個?就能讓這個參數變成可選參數,如代碼清單3所示。
代碼清單3
// 參數 age 可傳也可以不傳,如果傳了就必須是 number類型 function printDetail(name: string, age?: number): void { console.log(`name is ${name}, age is ${age ? age : '??'}`) } printDetail('Bella', 23) // 不會有類型錯誤 printDetail('Bella') // 不會有類型錯誤 printDetail('Bella', '3') // 有類型錯誤
函數的默認參數與可選參數類似,在調用函數的時候可以不給默認參數傳值,如果不傳值,那么這個參數就會取它的默認值。在函數參數的類型注釋的后面加一個 = ,再在 = 的后面跟一個具體的值,就能將這個參數指定為默認參數。修改代碼清單3得到代碼清單4。
代碼清單4
function printDetail(name: string, age: number = 23): void { console.log(`name is ${name}, age is ${age}`) }
在代碼清單4中,不需要在 printDetail 的函數體中判斷 ag e是否存在。如果調用 printDetail 的時候,沒有給 printDetail 傳遞第二個參數,那么 age 取值為 23。在調用函數的時候如果傳遞的參數值為 undefined,這相當于沒有傳參數值。
函數重載指的是函數名相同,但是參數列表不相同。JavaScript 沒有靜態類型檢查,所以 JavaScript 不支持函數重載,在 TypeScript 中支持函數重載,但是 TypeScript 中的函數重載只存在于它的編譯階段。
在TypeScript中函數重載的寫法如下:
function getDate(timestamp: number):number; function getDate(str: string): Date; function getDate(s: number| string): number | Date { if (typeof s === "number") { return s } else { return new Date(s) } }
上述代碼中的函數 getDate 有兩個重載,一個期望接受一個 number 類型參數,另一個期望接受一個 string 類型的參數。第一行和第二行的函數沒有函數體,它們被稱為重載簽名,第3行到第9行的函數有函數體,它被稱為實現簽名。
編寫重載函數時,重載簽名必須位于實現簽名的前面,并且實現簽名必須與所有的重載簽名兼容。代碼清單5是一個實現簽名與重載簽名不兼容的例子。
代碼清單5
function getMonth(timestamp: number): number function getMonth(date: Date): number function getMonth(d: Date): number { if (typeof d === 'number') { return new Date(d).getMonth() } else { return d.getMonth() } }
代碼清單5中的 getMonth 有兩個重載簽名,第一個重載簽名接受一個 number 類型的參數,第二個重載簽名接受一個 Date 類型的參數,但 getMonth 的實現簽名只接受一個Date 類型的參數,它與第一個重載簽名不兼容。在代碼清單5中,應該將 getMonth 的實現簽名中的參數 d 的數據類型改成 Date | string。
調用重載函數時,必須調用某個確定的重載,不能即可能調用第一個重載又可能調用另外的重載,以重載函數 getMonth 為例:
getMonth(2344553444) // 這是沒問題的 getMonth(new Date()) // 這是沒問題的 getMonth(Math.random() > 0.5 ? 2344553444: new Date()) // 有問題
上述代碼第三行不能在編譯階段確定它調用的是哪一個重載,如果你非要這么調用,那么你不能使用重載函數。
補充:在 TypeScript 中有一個通用的函數類型,那就是 Function,它表示所有的函數類型。
在 TypeScript 中,接口類型用于限制對象的形狀,即:對象有哪些屬性,以及這些屬性的數據類型是什么,在后文將接口類型簡稱為接口。有三種接口類型,分別是隱式接口,命名接口和匿名接口。
隱式接口
當創建一個帶有 key/value 的對象時,TypeScript 會通過檢查對象的屬性名和每個屬性值的數據類型去創建一個隱式接口,代碼如下:
const user = { name: 'bella', age: 23 } // TypeScript 創建的隱式接口為: { name: string; Age: number; }
匿名接口
匿名接口沒有名稱,它不能被重復使用,使用匿名接口會造成代碼冗余,隱式接口也是匿名接口。用匿名接口限制對象的形狀,代碼如下:
const student: { name: string; age: number } = { name: 'bella', age: 23 } const pig: { name: string; age: number } = { name: 'hua', age: 2 }
命名接口
在 TypeScript 中,使用 interface 關鍵字定義命名接口,命名接口可以讓代碼更加簡潔,因為它可以被重復使用。代碼如下:
// 定義接口類型 interface BaseInfo { name: string; age: number } // 用接口類型注釋對象的類型 const bella: BaseInfo = { name: 'bella', age: 23 } const hua: BaseInfo = { name: 'hua', age: 2 }
在介紹函數類型的時候介紹了函數的可選參數,接口的可選屬性與函數的可選參數類似,它指的是,在對象中可以有這個屬性也可以沒有這個屬性。接口的可選屬性的格式為:propertyName?: type,即:在屬性名與冒號之間加一個問號。在接口中定義可選屬性,能讓這個接口適用范圍更廣,但是它會帶來一些問題,比如:不能用可選屬性參與算術運算。
如果對象的某個屬性在創建之后不可修改,可以在創建接口的時候將這個屬性指定為只讀屬性,接口的只讀屬性的格式為:readonly propertyName: type,即:在屬性名的前面加上 readonly 關鍵字。對象的只讀屬性不能被單獨修改,但是可以將整個對象重復賦值,如代碼清單6所示。
代碼清單6
interface DepartmentInfo { departmentName: string; readonly departmentId: string } let department: DepartmentInfo = { departmentName: '研發部', departmentId: '1' } // 不能修改 id 屬性 department.id = '2' // line A類型檢查會報錯 // 將 department 對象重新賦值 department = { // line B類型檢查不會報錯 departmentName: '研發部', departmentId: '2' }
代碼清單6中的line A在編譯階段會報錯,line B 在編譯階段不會報錯。
如果要讓數組變成只讀的,能用 ReadonlyArray 代替 Array,也能在 Type[] 前加 readonly關鍵字,用法如下:
const myArr: ReadonlyArray<string> = ['1','2'] const myArr2: readonly string[] = ['1','2']
myArr 和 myArr2 上所有會導致數組發生變化的方法都會被移除,如:push,pop等。
與 class 類似,接口可以從其他接口中繼承屬性,與 class 不同的是,接口可以從多個接口中繼承。接口擴展用到的關鍵字是 extends,接口擴展能在命名接口的基礎上進一步提高代碼的可復用性,接口擴展的用法如代碼清單7所示。
代碼清單7
interface Staff extends BaseInfo, DepartmentInfo { staffId: string }
代碼清單7中的 Staff 會包含 BaseInfo 和 DepartmentInfo 中的所有屬性。如果 BaseInfo 和 DepartmentInfo 上存在同名但數據類型不兼容的屬性,那么 Staff 不能同時擴展 BaseInfo 和 DepartmentInfo。如果 Staff 上新增的屬性與 BaseInfo 或者 DepartmentInfo 上的屬性同名但數據類型不兼容,那么也不能擴展。
當同一個文件中聲明了多個同名的接口,TypeScript 會將這些同名接口中的屬性合并在一起,代碼如下所示:
interface Human { name: string; } interface Human { sex: string; } const Li: Human = { name: 'li', sex: '女' }
在某些時候,可能不確定對象有哪些屬性名,但屬性名對應的值的數據類型是確定的,這種情況可以用帶有索引簽名的接口來解決,用法如代碼清單8所示。
代碼清單8
interface Car { price: string; [attr: string]: number; // line A } const one: Car = { price: '3', size: 3.4 } const two: Car = { price: '4', 1: 4 }
代碼清單8中的 line A 對應的代碼就是接口的索引簽名,索引簽名 key 的數據類型只能是 string 或者是 number,value 的數據類型可以使用任何合法的 TypeScript 類型。用 Car 接口注釋的對象,一定要包含 price 屬性,并且 price 的值是 sting 類型,對象其他的屬性名只需要是字符串,屬性值是 number 類型就能滿足要求。
補充:數組和純JavaScript對象都是可索引的,所以能用可索引的接口去注釋它們。
上一節介紹了用函數類型表達式描述函數的簽名,除此之外,接口也能描述函數的簽名,代碼清單2中的 myFnType 可被改寫成下面這種形式:
interface myFnType { (a: number, b: number): void }
帶有匿名方法簽名的接口可用于描述函數,在 JavaScript 中,函數也是對象,因此在函數類型的接口上定義任何屬性都是合法的,用法如代碼清單9所示。
代碼清單9
// 函數類型的接口 interface Arithmetic { (a: number, b: number): number; // 匿名函數簽名 type: string; } function calculate (a: number, b: number): number { return a + b } calculate.type = 'add' const add: Arithmetic = calculate console.log(add(2,1)) // 3 console.log(add.type) // add
在項目中,有些函數是構造函數,為了類型安全應該通過 new 關鍵字調用它,但在 JavaScript 領域沒有這種限制,幸運的是,在 TypeScript 中,構造函數類型的接口可描述構造函數。將代碼清單9中 Arithmetic 改寫成代碼清單10中的形式,使函數 add 只能通過 new 關鍵字調用。
代碼清單10
// 構造函數類型的接口 interface Arithmetic { new (a: number, b: number): Add ; // 在匿名函數簽名前加 new 關鍵字,注意返回值類型 type: string; }
ES2015 中的 class 與構造函數是一回事,因此構造函數類型的接口可用于描述 class,用法如代碼清單11所示,代碼清單11沿用代碼清單10中的 Arithmetic。
代碼清單11
class Add { a: number b: number static type: string constructor(a: number, b: number) { this.a = a; this.b = b; } calculate() { return this.a + this.b } } function createAdd(isOdd: boolean, Ctor: Arithmetic) { return isOdd ? new Ctor(1,3) : new Ctor(2,4) } createAdd(false, Add)
本節只介紹類在TypeScript類型系統層面的知識。
使用 implements 關鍵字讓類實現某個特定的接口,它只檢查類的公共實例字段是否滿足特定的接口,并且不改變字段的類型。implements 的用法如代碼清單12所示。
代碼清單12
interface User { name: string; nickName: string; printName: () => void } // TypeScript 程序會報錯 class UserImplement implements User { name: string = 'Bella' // 這是私有字段 private nickName: string = 'hu' printName() { console.log(this.name) } }
在代碼清單12中,UserImplement 類實現 User 接口,但 UserImplement 類將 nickName 定義為私有字段,這使 UserImplement 實例的公共字段的形狀與 User 接口不兼容,所以代碼清單12會報錯。
類的實例端類型
當創建一個類時,TypeScript 會為這個類創建一個隱式接口,這個隱式接口就是類的實例端類型,它包含類的所有非靜態成員的形狀,當使用 :ClassName 注釋變量的類型時,TypeScript會檢查變量的形狀是否滿足類的實例端類型。
類的靜態端類型
類實際上是一個構造函數,在 JavaScript 中,函數也是對象,它可以有自己的屬性。類的靜態端類型用于描述構造函數的形狀,包括構造函數的參數、返回值和它的靜態成員,:typeof ClassName返回類的靜態端類型。
this 可以作為類型在類或接口的非靜態成員中使用,此時,this 不表示某個特定的類型,它動態的指向當前類的實例端類型。當存在繼承關系的時候,this 類型的動態性就能被體現出來,下面用代碼清單13加以說明。
代碼清單13
interface U { relationship?: this printName(instance: this): void } class User implements U { relationship?: this; name: string = 'unknown' printName(instance: this) { console.log(instance.name) } setRelationship(relationship: this) { this.relationship = relationship } } class Student extends User { grade: number = 0 } const user1 = new User() const student = new Student() const otherStudent = new Student() student.printName(student) // 沒有類型錯誤,此時printName能接受參數類型為 Student的類型 student.setRelationship(otherStudent) // 沒有類型錯誤,此時printName能接受參數類型為 Student的類型 student.printName(user1) // 有類型錯誤,此時printName能接受參數類型為 Student的類型 user.printName(student) // 沒有類型錯誤,此時printName能接受參數類型為 User的類型
代碼清單13中,Student 是 User 的子類,它在 User 的基礎上新增了一個非靜態成員,所以 User 類型的參數不能賦給 Student 類型的參數,但 Student 類型的參數能賦給 User 類型的參數。當用子類實例調用 printName 方法時,printName 能接受參數類型為子類的類型,當用父類實例調用 printName 方法時,printName 能接受的參數類型為父類的類型。
默認情況下,函數中 this 的值取決于函數的調用方式,在 TypeScript 中,如果將 this 作為函數的參數,那么 TypeScript 會檢查調用函數時是否帶有正確的上下文。this 必須是第一個參數,并且只存在于編譯階段,在箭頭函數中不能包含 this 參數。下面通過代碼清單14加以說明。
代碼清單14
class User{ name: string = 'unknown' // 只能在當前類的上下文中調用 printName 方法,注意 this 類型的動態性 printName(this: this) { console.log(this.name) } } const user = new User() user.printName() // 沒問題 const printName = user.printName printName() // 有問題
在 TypeScript 中使用 enum 關鍵字創建枚舉,枚舉是一組命名常量,它可以是一組字符串值,也能是一組數值,也能將兩者混合使用。枚舉分為兩類,分別是常規枚舉和常量枚舉。
常規枚舉
常規枚舉會作為普通的 JavaScript 對象注入到編譯后的 JavaScript 代碼中,在源代碼中訪問常規枚舉的成員,將在輸出代碼中轉換成訪問對象的屬性。下面的代碼定義了一個常規枚舉:
enum Tab { one, two } console.log(Tab) // 打印對象
常量枚舉
聲明枚舉時,將 const 關鍵字放在 enum 之前,就能聲明一個常量枚舉。常量枚舉不會作為 JavaScript 對象注入到編譯后的 JavaScript 代碼中,這使產生的 JavaScript 代碼更少,在源代碼中訪問常量枚舉的成員,將在輸出代碼中轉換為訪問枚舉成員的字面量。下面的代碼定義了一個常量枚舉:
const enum Tab { one, two } console.log(Tab) // ts 程序報錯 console.log(Tab.one) // 在 js 代碼中被轉換為:console.log(0 /* one */);
常量枚舉比常規枚舉產生的代碼量更少,它能減少程序的開銷,但是常量枚舉的使用范圍更小,它只能在屬性、索引訪問表達式、模塊導入/導出或類型注釋中使用。
當我們定義一個枚舉時,TypeScript 也將定義一個同名的類型,這個類型稱為枚舉類型,用此類型注釋的變量必須引用此枚舉的成員。由于 TypeScript 類型系統是一個結構化的類型系統,所以,除了可以將枚舉成員賦給枚舉類型的變量之外,還能將枚舉的成員的字面量賦值給枚舉類型的變量。代碼如下所示:
interface Page { name: string; tabIndex: Tab; } const page: Page = { name: '首頁', tabIndex: Tab.two // 將枚舉成員賦給枚舉類型的變量 } page.tabIndex = 0 // 將數值字面量賦給枚舉類型的變量,不推薦!!!
枚舉類型是一個集合類型,枚舉成員有它們的類型。如果變量的類型是枚舉的成員類型,那么不能將枚舉中的其他成員賦給該變量。代碼如下:
let index: Tab.one = Tab.one; index = Tab.two; // ts 程序報錯
可以顯式地為枚舉成員設置數字或者字符串值,那些沒有顯式提供值的成員將通過查看前一個成員的值自動遞增,如果前一個成員的值不是數值就會報錯,枚舉成員的值從0開始計數。TypeScript 將枚舉的成員根據它的初始化時機分為兩大類,分別為:常量成員與計算成員。
常量成員
如果枚舉成員的值在編譯階段就能確定,這個成員是常量成員。通過如下的幾種方式初始化能在編譯階段確定值:
不顯式初始化,并且前一個成員是number類型
用數字或者字符串字面量
用前面定義的枚舉常量成員
將+、-、~這些一元運算符用于枚舉常量成員
將+, -, *, /, %, <<, >>, >>>, &, |, ^這些二進制操作用于枚舉常量成員
定義枚舉常量成員的代碼如下:
enum MyEnum { one, two = Tab.two, three = -two, four = two + 3, five = four << 4 }
計算成員
如果枚舉成員的值在運行階段才能確定,這個成員就是計算成員。代碼如下所示:
enum computedMember { one = Math.random(), two = one + 2 }
補充:計算成員不能位于常量枚舉(即:const 枚舉)中。在包含字符串成員的枚舉中,枚舉成員不能用表達式去初始化。
字面量類型就是將一個特定的字面量作為類型去注釋變量的類型,字面量類型可以是:字符串字面量,數值字面量和布爾值字面量。用 const 聲明變量,并且不給這個變量設置數據類型,而是將一個具體的字符串、數值或者布爾值賦給它,TypeScript 會給變量隱式的注釋字面量類型。代碼如下所示:
const type = 'one' // 等同于 const type: 'one' = 'one' // 只能將 Bella 賦值給變量 hello let hello: 'Bella' = 'Bella' hello = 'one' // 類型錯誤 // 這個函數的返回值只能是true,它的第二個參數要么沒有,要么為 3 function compare(one: string, two?: 3): true { console.log(one, two) return true }
用管道(|)操作符將一個或者一個以上的數據類型組合在一起會形成一個新的數據類型,這個新的數據類型就是聯合類型,這一種邏輯或。可以從所有的類型創建聯合類型,比如:接口,數值,字符串等。在 TypeScript 中只允許使用聯合類型中每個成員類型都存在的屬性或者方法,否則,程序會報錯。
聯合類型的用法如下:
// 能將字符串和數值類型賦值給變量 type let type: string|number = 1 type = '1' // 能將 0、1或布爾值賦值給變量 result let result: 0 | 1 | boolean = true result = 2 // 類型錯誤 interface User { name: string } interface Student extends User{ grade: number; } function printInfo(person: User|Student) { // 在這里會有類型錯誤,因為 grade 屬性只存在 Student類型中 console.log(person.name + ':' + person.grade) }
提示:任何類型與any類型進行聯合操作得到的新類型是 any 類型,任何非 never 類型與 never 類型進行聯合操作得到的新類型是非 never 類型。
在 TypeScript 中,用 & 操作符連接兩個類型,它會返回一個新的類型,這個新類型被稱為交叉類型,它包含了兩種類型中的屬性,能與這兩種類型中的任何一種兼容。交叉類型相當于將兩個類型的屬性合在一起形成一個新類型。
當兩個接口 交叉時,這兩個接口中的公共屬性也會交叉,接口 交叉與接口擴展有些類似,不同點是:如果擴展的接口中存在同名但是不兼容的屬性,那么不能進行接口擴展,但是能夠進行接口 交叉,如代碼清單15所示。
代碼清單15
interface User { name: string; age: number } interface Student { name: string; age: string; grade: number; } // 不能進行接口擴展,因為 User 和 Student 中的 age 屬性不兼容 interface TypeFromExtends extends User, Student {} // 能夠進行接口 交叉 type TypeFromIntersection = User & Student
User 中的 age 是 number 類型,Student 中的 age 是 string 類型,User & Student 會導致 number & string,由于不存在一個值既是數值又是字符串,所以 number & string 返回的類型為 never。代碼清單15中 TypeFromIntersection 的形狀如下所示:
interface TypeFromIntersection { name: string; grade: number; age: never }
提示:任何類型與 any 類型進行交叉操作得到的新類型是 any 類型,任何類型與 never 類型交叉操作得到的新類型是 never 類型。
泛型是指泛型類型,只存在于編譯階段,使用泛型能創建出可組合的動態類型,這提高了類型的可重用性。泛型有一個存儲類型的變量,也可以將它稱為類型參數,能在其他地方用它注釋變量的類型。泛型可用在函數、接口、類等類型中,代碼清單16是一個使用泛型的簡單示例。
代碼清單16。
function genericFunc<T>(a: T):T { return a; } console.log( genericFunc<string>('a').toUpperCase() ) // lineA console.log( genericFunc<number>(3).toFixed() ) // lineB
代碼清單16,genericFunc 函數中的 T 是類型參數,在 lineA 調用 genericFunc 函數,T 是 string 類型,在 lineB 調用 genericFunc 函數,T 是 number 類型。
代碼清單16中的 genericFunc 函數是一個泛型函數,它的函數類型為:<T>(a: T) => T
。genericFunc 函數只有一個類型參數,實際上它可以用多個類型參數,并且參數名可以是任何合法的變量名,修改代碼清單16使 genericFunc 有兩個類型參數,修改結果如下:
function genericFunc<T,U>(a: T, b: U): [T, U] { return [a, b]; } console.log( genericFunc<string, number>('a', 3) ) // lineA console.log( genericFunc(3, 'a')) // lineB
上述代碼 lineB 的函數調用沒有給類型參數傳值,但它能夠工作,這是因為 TypeScript 能推導出T為 number,U 為 string。
在前面介紹過可以用接口類型描述函數,實際上也能在接口中使用泛型語法描述泛型函數。示例代碼如下:
interface genericFunc { <T>(a: T): T }
上述代碼定義的 genericFunc 接口與代碼清單16中的 genericFunc 函數類型一樣。
在 TypeScript 中,接口類型用于限制對象的形狀,對象可能有多個屬性,可以用接口的類型參數去注釋這些屬性的數據類型。下面的示例將類型參數提升到接口名的后面,使得接口中的每個成員都能引用它。
interface genericInterface<T> { a: T, getA: () => T } // 給接口傳遞類型變量 const myObj: genericInterface<number> = { // lineA a: 2, getA: () => { return 2 } }
上述代碼,當在 lineA 使用 genericInterface 泛型接口時,將 number 類型傳遞給了 T,所以 myObj 的 a 屬性必須是 number 類型,并且 getA 方法的返回值的類型也必須是 number 類型。
接口可以有類型參數,接口中的函數字段也能有自己的類型參數,示例代碼如下:
interface genericInterface<T> { a: T, printInfo<U>(info: U): void } const myObj2: genericInterface<number> = { // lineA a: 3, printInfo: <U>(info: U): void => { console.log(info) } } myObj2.printInfo<string>('e') // lineB
上述代碼中類型參數T是接口的類型參數,在使用接口的時候就要傳,參數類型 U 是 printInfo 方法的類型參數,在調用 printInfo 方法的時候傳,U 與 T 不同的是,U 只能在 printInfo 函數中使用,而 T 可以在接口的所有成員上使用。
泛型類與泛型接口類似,它也將類型參數放在類名的后面,在類的所有實例字段中都能使用類的類型參數,但在靜態字段中不能使用類的類型參數。示例代碼如下:
class Information<T, U> { detail: T; title: U; constructor(detail: T, title: U) { this.detail = detail this.title = title } } // 在實例化類的時候將類型傳給類型參數 new Information<string, string>('detail', 'title')
當泛型類存在繼承關系時,父類的類型參數通過子類傳遞給它。示例代碼如下:
class SubClass<C, K> extends Information<C, K> {/* do something*/} new SubClass<number, string>(2,3)
上述代碼在實例化 SubClass 時,將 number 傳給了 C,將 string 傳給了 K,然后 C 和 K 又傳給 Information。
補充:定義泛型函數,泛型接口和泛型類的語法或多或少存在差異,但有一個共同點是,它們的類型參數是在使用泛型的時候傳,而非在定義泛型的時候傳,這使泛型具有動態性,提高了類型的可重用性。
類類型由靜態端類型和實例端類型兩部分組成,現在將泛型運用到工廠函數中,讓它接受任何類作為參數,并返回該類的實例。示例代碼如下:
class User{/**do something */} class Tools{/**do something */} function genericFactory<T>(Ctor: new () => T):T { return new Ctor() } const user: User = genericFactory<User>(User) // lineA const tools: Tools = genericFactory<Tools>(Tools) // lineB
上述代碼中,genericFactory的類型參數T必須是類的實例端類型,通過類名就能引用到類的實例端類型,所以在lineA和lineB分別將User和Tools傳給了T。
extends 關鍵字可用于接口擴展和類擴展,這個關鍵字也能用于約束泛型類型參數的值,比如:<T extends User,K>
,這意味著T的值必須擴展自 User 類型,而 K 的值可以是任何合法的類型。下面是用 extends 關鍵字進行類型約束的示例代碼:
interface User { name: string } interface Student extends User { age: number } const ci = { name: 'Ci', age: 2 } // T 的值必須擴展自 User 類型,K 的值可以是任何類型 function print<T extends User, K>(user: T, b: K): void{/**do somethine */} print<Student, string>(ci, '3') // 沒毛病 print<string, string>('tt', 'kk') // 不滿足泛型約束,因為 string不是擴展自 User
上述代碼中的類型參數T擴展自User接口,實際上泛型類型參數能擴展自任何合法的 TypeScript 類型,比如:字符串,聯合類型,函數類型等。
補充:如果泛型約束是:<T extends string | number>
,那么T的值可以是任何字符串或者數值。
在前面的內容中介紹過,類型參數是一個變量,可以用它注釋其他變量的類型,它也能約束其他類型參數。用法如下:
function callFunc<T extends FuncType<U>, U>(func: T, arg: U): void interface myInterface<C, K extends keyof C>
上述代碼中的 callFunc 有兩個類型參數,分別是T和U,U可以是任何類型,T 必須擴展自 FuncType<U>
,FuncType<U>
中的 U 指向 callFunc 的類型參數 U。myInterface 也有兩個類型參數,分別是 C 和K ,K 擴展自 keyof C,keyof C 中 C 指向 myInterface 的類型參數 C。
條件類型的語法為:Type1 extends Type2 ? TrueType: FalseType
,如果 Type1 擴展自 Type2,那么表達式將得到 TrueType,否則得到 FalseType,這個表達式只存在于編譯階段,并且只用于類型注釋和類型別名。下面是一個將條件類型與泛型配合使用的示例:
type MyType<T> = T extends string ? T[]: never[]; let stringArr: MyType<string> // string[] let numberArr: MyType<number> // never[] let unionArr: MyType<1|'1'|string> // never[] | string[]
上述代碼中的 MyType 接受一個類型參數T,在編譯階段 TypeScript 會根據 T 是否繼承自 string,去動態的計算出 MyType 的數據類型。如果 T 是聯合類型,那么會判斷聯合類型中的每一個成員類型是否擴展自 string,所以最后一行中的 unionArr 類型為 never[] | string[]。
關于“TypeScript如何自定義數據類型”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。