TypeScript類型系統(tǒng)自定義數(shù)據(jù)類型教程示例
TypeScript 類型系統(tǒng)和自定義數(shù)據(jù)類型
TypeScript 在 JavaScript 的基礎(chǔ)上增加了靜態(tài)類型系統(tǒng),它使代碼的可讀性更強(qiáng),讓代碼重構(gòu)變得更容易。但是對(duì) TypeScript 而言,它的靜態(tài)類型系統(tǒng)是可選的,這讓JavaScript 程序很容易就能遷移到 TypeScript 程序。
什么是類型系統(tǒng)
類型系統(tǒng)是一組規(guī)則,它用來(lái)規(guī)定編程語(yǔ)言如何將變量、類、函數(shù)等識(shí)別為不同的類型,如何操作這些類型以及不同類型之間的關(guān)系。類型系統(tǒng)分為靜態(tài)類型系統(tǒng)和動(dòng)態(tài)類型系統(tǒng)。
- 動(dòng)態(tài)類型系統(tǒng)
JavaScript 是一種動(dòng)態(tài)類型的編程語(yǔ)言,它在運(yùn)行階段進(jìn)行類型檢查,所以與類型相關(guān)的錯(cuò)誤要在運(yùn)行階段才會(huì)被暴露出來(lái)。
- 靜態(tài)類型系統(tǒng)
TypeScript 在 JavaScript 的基礎(chǔ)上增加了靜態(tài)類型系統(tǒng),它使 TypeScript 程序在編譯階段就進(jìn)行類型檢查,與類型相關(guān)的錯(cuò)誤在編譯階段就能暴露出來(lái),這使開(kāi)發(fā)人員能提前發(fā)現(xiàn)類型錯(cuò)誤。
TypeScript 的類型系統(tǒng)是一個(gè)結(jié)構(gòu)化類型系統(tǒng),在結(jié)構(gòu)化類型系統(tǒng)中,如果兩個(gè)類型有相同的結(jié)構(gòu),不論它們的類型名是否相同,則認(rèn)為它們是相同類型。這意味著類型名不重要,只要結(jié)構(gòu)是匹配的,類型就兼容。
函數(shù)類型
在 TypeScript 中有多種方式去描述函數(shù)的簽名,例如:函數(shù)類型表達(dá)式、接口類型。在這里先介紹如何用函數(shù)類型表達(dá)式描述函數(shù)的簽名。函數(shù)類型表達(dá)式語(yǔ)法如下:
// 這是一個(gè)函數(shù)類型,它描述的函數(shù)接受兩個(gè)參數(shù),分別是name和age,name是string類型,age是number類型,這個(gè)函數(shù)沒(méi)有返回值 (name: string, age: number) => void // lineA // 這是一個(gè)函數(shù)類型,它描述的函數(shù)接受一個(gè)參數(shù),這個(gè)參數(shù)是number類型,函數(shù)的返回值的類型是 number (a: number) => number
函數(shù)類型表達(dá)式語(yǔ)法與 ES2015 的箭頭函數(shù)語(yǔ)法很相似,但是函數(shù)類型表達(dá)式不會(huì)創(chuàng)建任何函數(shù),它只存在于 TypeScript 編譯時(shí)。從上述代碼可以看出,函數(shù)的返回值類型放在箭頭符號(hào)(=>)的后面,函數(shù)的參數(shù)類型以 :type 的形式放在參數(shù)的后面。代碼清單1演示了如何使用函數(shù)類型表達(dá)式。
代碼清單1
// 聲明一個(gè)名為 startHandle 的變量,它的數(shù)據(jù)類型是函數(shù),它沒(méi)有返回值,它接受一個(gè)名為fn的參數(shù),并且 fn 的數(shù)據(jù)類型也是函數(shù) let startHandle: (fn: (a: number, b: number) => void) => void // line A // 在這里將一個(gè)箭頭函數(shù)賦值給 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 乍一看不好理解,主要是它太長(zhǎng)了,而且存在冗余的部分,可以使用類型別名解決這個(gè)問(wèn)題。
類型別名
定義類型別名需要用到的關(guān)鍵字是 type,用法如下:
type myFnType = (a: number, b: number) => void
接下來(lái)就能在代碼中用 myFnType 代替 (a: number, b: number) => void,讓代碼更加的簡(jiǎn)潔。修改代碼清單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 簡(jiǎn)潔很多,而且也更加容易理解。
可選參數(shù)
代碼清單1和代碼清單1中的函數(shù)類型,它們每一個(gè)參數(shù)都是必填的,但在某些情況下,我們要讓函數(shù)參數(shù)是可選的,在函數(shù)參數(shù)的類型注釋的前面加一個(gè)?就能讓這個(gè)參數(shù)變成可選參數(shù),如代碼清單3所示。
代碼清單3
// 參數(shù) age 可傳也可以不傳,如果傳了就必須是 number類型 function printDetail(name: string, age?: number): void { console.log(`name is ${name}, age is ${age ? age : '??'}`) } printDetail('Bella', 23) // 不會(huì)有類型錯(cuò)誤 printDetail('Bella') // 不會(huì)有類型錯(cuò)誤 printDetail('Bella', '3') // 有類型錯(cuò)誤
默認(rèn)參數(shù)
函數(shù)的默認(rèn)參數(shù)與可選參數(shù)類似,在調(diào)用函數(shù)的時(shí)候可以不給默認(rèn)參數(shù)傳值,如果不傳值,那么這個(gè)參數(shù)就會(huì)取它的默認(rèn)值。在函數(shù)參數(shù)的類型注釋的后面加一個(gè) = ,再在 = 的后面跟一個(gè)具體的值,就能將這個(gè)參數(shù)指定為默認(rèn)參數(shù)。修改代碼清單3得到代碼清單4。
代碼清單4
function printDetail(name: string, age: number = 23): void { console.log(`name is ${name}, age is ${age}`) }
在代碼清單4中,不需要在 printDetail 的函數(shù)體中判斷 ag e是否存在。如果調(diào)用 printDetail 的時(shí)候,沒(méi)有給 printDetail 傳遞第二個(gè)參數(shù),那么 age 取值為 23。在調(diào)用函數(shù)的時(shí)候如果傳遞的參數(shù)值為 undefined,這相當(dāng)于沒(méi)有傳參數(shù)值。
函數(shù)重載
函數(shù)重載指的是函數(shù)名相同,但是參數(shù)列表不相同。JavaScript 沒(méi)有靜態(tài)類型檢查,所以 JavaScript 不支持函數(shù)重載,在 TypeScript 中支持函數(shù)重載,但是 TypeScript 中的函數(shù)重載只存在于它的編譯階段。
在TypeScript中函數(shù)重載的寫(xiě)法如下:
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) } }
上述代碼中的函數(shù) getDate 有兩個(gè)重載,一個(gè)期望接受一個(gè) number 類型參數(shù),另一個(gè)期望接受一個(gè) string 類型的參數(shù)。第一行和第二行的函數(shù)沒(méi)有函數(shù)體,它們被稱為重載簽名,第3行到第9行的函數(shù)有函數(shù)體,它被稱為實(shí)現(xiàn)簽名。
編寫(xiě)重載函數(shù)時(shí),重載簽名必須位于實(shí)現(xiàn)簽名的前面,并且實(shí)現(xiàn)簽名必須與所有的重載簽名兼容。代碼清單5是一個(gè)實(shí)現(xiàn)簽名與重載簽名不兼容的例子。
代碼清單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 有兩個(gè)重載簽名,第一個(gè)重載簽名接受一個(gè) number 類型的參數(shù),第二個(gè)重載簽名接受一個(gè) Date 類型的參數(shù),但 getMonth 的實(shí)現(xiàn)簽名只接受一個(gè)Date 類型的參數(shù),它與第一個(gè)重載簽名不兼容。在代碼清單5中,應(yīng)該將 getMonth 的實(shí)現(xiàn)簽名中的參數(shù) d 的數(shù)據(jù)類型改成 Date | string。
調(diào)用重載函數(shù)時(shí),必須調(diào)用某個(gè)確定的重載,不能即可能調(diào)用第一個(gè)重載又可能調(diào)用另外的重載,以重載函數(shù) getMonth 為例:
getMonth(2344553444) // 這是沒(méi)問(wèn)題的 getMonth(new Date()) // 這是沒(méi)問(wèn)題的 getMonth(Math.random() > 0.5 ? 2344553444: new Date()) // 有問(wèn)題
上述代碼第三行不能在編譯階段確定它調(diào)用的是哪一個(gè)重載,如果你非要這么調(diào)用,那么你不能使用重載函數(shù)。
補(bǔ)充:在 TypeScript 中有一個(gè)通用的函數(shù)類型,那就是 Function,它表示所有的函數(shù)類型。
接口類型
在 TypeScript 中,接口類型用于限制對(duì)象的形狀,即:對(duì)象有哪些屬性,以及這些屬性的數(shù)據(jù)類型是什么,在后文將接口類型簡(jiǎn)稱為接口。有三種接口類型,分別是隱式接口,命名接口和匿名接口。
隱式接口
當(dāng)創(chuàng)建一個(gè)帶有 key/value 的對(duì)象時(shí),TypeScript 會(huì)通過(guò)檢查對(duì)象的屬性名和每個(gè)屬性值的數(shù)據(jù)類型去創(chuàng)建一個(gè)隱式接口,代碼如下:
const user = { name: 'bella', age: 23 } // TypeScript 創(chuàng)建的隱式接口為: { name: string; Age: number; }
匿名接口
匿名接口沒(méi)有名稱,它不能被重復(fù)使用,使用匿名接口會(huì)造成代碼冗余,隱式接口也是匿名接口。用匿名接口限制對(duì)象的形狀,代碼如下:
const student: { name: string; age: number } = { name: 'bella', age: 23 } const pig: { name: string; age: number } = { name: 'hua', age: 2 }
命名接口
在 TypeScript 中,使用 interface 關(guān)鍵字定義命名接口,命名接口可以讓代碼更加簡(jiǎn)潔,因?yàn)樗梢员恢貜?fù)使用。代碼如下:
// 定義接口類型 interface BaseInfo { name: string; age: number } // 用接口類型注釋對(duì)象的類型 const bella: BaseInfo = { name: 'bella', age: 23 } const hua: BaseInfo = { name: 'hua', age: 2 }
可選屬性
在介紹函數(shù)類型的時(shí)候介紹了函數(shù)的可選參數(shù),接口的可選屬性與函數(shù)的可選參數(shù)類似,它指的是,在對(duì)象中可以有這個(gè)屬性也可以沒(méi)有這個(gè)屬性。接口的可選屬性的格式為:propertyName?: type,即:在屬性名與冒號(hào)之間加一個(gè)問(wèn)號(hào)。在接口中定義可選屬性,能讓這個(gè)接口適用范圍更廣,但是它會(huì)帶來(lái)一些問(wèn)題,比如:不能用可選屬性參與算術(shù)運(yùn)算。
只讀屬性
如果對(duì)象的某個(gè)屬性在創(chuàng)建之后不可修改,可以在創(chuàng)建接口的時(shí)候?qū)⑦@個(gè)屬性指定為只讀屬性,接口的只讀屬性的格式為:readonly propertyName: type,即:在屬性名的前面加上 readonly 關(guān)鍵字。對(duì)象的只讀屬性不能被單獨(dú)修改,但是可以將整個(gè)對(duì)象重復(fù)賦值,如代碼清單6所示。
代碼清單6
interface DepartmentInfo { departmentName: string; readonly departmentId: string } let department: DepartmentInfo = { departmentName: '研發(fā)部', departmentId: '1' } // 不能修改 id 屬性 department.id = '2' // line A類型檢查會(huì)報(bào)錯(cuò) // 將 department 對(duì)象重新賦值 department = { // line B類型檢查不會(huì)報(bào)錯(cuò) departmentName: '研發(fā)部', departmentId: '2' }
代碼清單6中的line A在編譯階段會(huì)報(bào)錯(cuò),line B 在編譯階段不會(huì)報(bào)錯(cuò)。
如果要讓數(shù)組變成只讀的,能用 ReadonlyArray 代替 Array,也能在 Type[] 前加 readonly關(guān)鍵字,用法如下:
const myArr: ReadonlyArray<string> = ['1','2'] const myArr2: readonly string[] = ['1','2']
myArr 和 myArr2 上所有會(huì)導(dǎo)致數(shù)組發(fā)生變化的方法都會(huì)被移除,如:push,pop等。
接口擴(kuò)展
與 class 類似,接口可以從其他接口中繼承屬性,與 class 不同的是,接口可以從多個(gè)接口中繼承。接口擴(kuò)展用到的關(guān)鍵字是 extends,接口擴(kuò)展能在命名接口的基礎(chǔ)上進(jìn)一步提高代碼的可復(fù)用性,接口擴(kuò)展的用法如代碼清單7所示。
代碼清單7
interface Staff extends BaseInfo, DepartmentInfo { staffId: string }
代碼清單7中的 Staff 會(huì)包含 BaseInfo 和 DepartmentInfo 中的所有屬性。如果 BaseInfo 和 DepartmentInfo 上存在同名但數(shù)據(jù)類型不兼容的屬性,那么 Staff 不能同時(shí)擴(kuò)展 BaseInfo 和 DepartmentInfo。如果 Staff 上新增的屬性與 BaseInfo 或者 DepartmentInfo 上的屬性同名但數(shù)據(jù)類型不兼容,那么也不能擴(kuò)展。
多重接口聲明
當(dāng)同一個(gè)文件中聲明了多個(gè)同名的接口,TypeScript 會(huì)將這些同名接口中的屬性合并在一起,代碼如下所示:
interface Human { name: string; } interface Human { sex: string; } const Li: Human = { name: 'li', sex: '女' }
接口的索引簽名
在某些時(shí)候,可能不確定對(duì)象有哪些屬性名,但屬性名對(duì)應(yīng)的值的數(shù)據(jù)類型是確定的,這種情況可以用帶有索引簽名的接口來(lái)解決,用法如代碼清單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 對(duì)應(yīng)的代碼就是接口的索引簽名,索引簽名 key 的數(shù)據(jù)類型只能是 string 或者是 number,value 的數(shù)據(jù)類型可以使用任何合法的 TypeScript 類型。用 Car 接口注釋的對(duì)象,一定要包含 price 屬性,并且 price 的值是 sting 類型,對(duì)象其他的屬性名只需要是字符串,屬性值是 number 類型就能滿足要求。
補(bǔ)充:數(shù)組和純JavaScript對(duì)象都是可索引的,所以能用可索引的接口去注釋它們。
用接口描述函數(shù)
上一節(jié)介紹了用函數(shù)類型表達(dá)式描述函數(shù)的簽名,除此之外,接口也能描述函數(shù)的簽名,代碼清單2中的 myFnType 可被改寫(xiě)成下面這種形式:
interface myFnType { (a: number, b: number): void }
帶有匿名方法簽名的接口可用于描述函數(shù),在 JavaScript 中,函數(shù)也是對(duì)象,因此在函數(shù)類型的接口上定義任何屬性都是合法的,用法如代碼清單9所示。
代碼清單9
// 函數(shù)類型的接口 interface Arithmetic { (a: number, b: number): number; // 匿名函數(shù)簽名 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
在項(xiàng)目中,有些函數(shù)是構(gòu)造函數(shù),為了類型安全應(yīng)該通過(guò) new 關(guān)鍵字調(diào)用它,但在 JavaScript 領(lǐng)域沒(méi)有這種限制,幸運(yùn)的是,在 TypeScript 中,構(gòu)造函數(shù)類型的接口可描述構(gòu)造函數(shù)。將代碼清單9中 Arithmetic 改寫(xiě)成代碼清單10中的形式,使函數(shù) add 只能通過(guò) new 關(guān)鍵字調(diào)用。
代碼清單10
// 構(gòu)造函數(shù)類型的接口 interface Arithmetic { new (a: number, b: number): Add ; // 在匿名函數(shù)簽名前加 new 關(guān)鍵字,注意返回值類型 type: string; }
ES2015 中的 class 與構(gòu)造函數(shù)是一回事,因此構(gòu)造函數(shù)類型的接口可用于描述 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)
類類型
本節(jié)只介紹類在TypeScript類型系統(tǒng)層面的知識(shí)。
implements關(guān)鍵字
使用 implements 關(guān)鍵字讓類實(shí)現(xiàn)某個(gè)特定的接口,它只檢查類的公共實(shí)例字段是否滿足特定的接口,并且不改變字段的類型。implements 的用法如代碼清單12所示。
代碼清單12
interface User { name: string; nickName: string; printName: () => void } // TypeScript 程序會(huì)報(bào)錯(cuò) class UserImplement implements User { name: string = 'Bella' // 這是私有字段 private nickName: string = 'hu' printName() { console.log(this.name) } }
在代碼清單12中,UserImplement 類實(shí)現(xiàn) User 接口,但 UserImplement 類將 nickName 定義為私有字段,這使 UserImplement 實(shí)例的公共字段的形狀與 User 接口不兼容,所以代碼清單12會(huì)報(bào)錯(cuò)。
類的靜態(tài)端類型和實(shí)例端類型
類的實(shí)例端類型
當(dāng)創(chuàng)建一個(gè)類時(shí),TypeScript 會(huì)為這個(gè)類創(chuàng)建一個(gè)隱式接口,這個(gè)隱式接口就是類的實(shí)例端類型,它包含類的所有非靜態(tài)成員的形狀,當(dāng)使用 :ClassName 注釋變量的類型時(shí),TypeScript會(huì)檢查變量的形狀是否滿足類的實(shí)例端類型。
類的靜態(tài)端類型
類實(shí)際上是一個(gè)構(gòu)造函數(shù),在 JavaScript 中,函數(shù)也是對(duì)象,它可以有自己的屬性。類的靜態(tài)端類型用于描述構(gòu)造函數(shù)的形狀,包括構(gòu)造函數(shù)的參數(shù)、返回值和它的靜態(tài)成員,:typeof ClassName返回類的靜態(tài)端類型。
將 this 作為類型
this 可以作為類型在類或接口的非靜態(tài)成員中使用,此時(shí),this 不表示某個(gè)特定的類型,它動(dòng)態(tài)的指向當(dāng)前類的實(shí)例端類型。當(dāng)存在繼承關(guān)系的時(shí)候,this 類型的動(dòng)態(tài)性就能被體現(xiàn)出來(lái),下面用代碼清單13加以說(shuō)明。
代碼清單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) // 沒(méi)有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 student.setRelationship(otherStudent) // 沒(méi)有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 student.printName(user1) // 有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 user.printName(student) // 沒(méi)有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 User的類型
代碼清單13中,Student 是 User 的子類,它在 User 的基礎(chǔ)上新增了一個(gè)非靜態(tài)成員,所以 User 類型的參數(shù)不能賦給 Student 類型的參數(shù),但 Student 類型的參數(shù)能賦給 User 類型的參數(shù)。當(dāng)用子類實(shí)例調(diào)用 printName 方法時(shí),printName 能接受參數(shù)類型為子類的類型,當(dāng)用父類實(shí)例調(diào)用 printName 方法時(shí),printName 能接受的參數(shù)類型為父類的類型。
將 this 作為參數(shù)
默認(rèn)情況下,函數(shù)中 this 的值取決于函數(shù)的調(diào)用方式,在 TypeScript 中,如果將 this 作為函數(shù)的參數(shù),那么 TypeScript 會(huì)檢查調(diào)用函數(shù)時(shí)是否帶有正確的上下文。this 必須是第一個(gè)參數(shù),并且只存在于編譯階段,在箭頭函數(shù)中不能包含 this 參數(shù)。下面通過(guò)代碼清單14加以說(shuō)明。
代碼清單14
class User{ name: string = 'unknown' // 只能在當(dāng)前類的上下文中調(diào)用 printName 方法,注意 this 類型的動(dòng)態(tài)性 printName(this: this) { console.log(this.name) } } const user = new User() user.printName() // 沒(méi)問(wèn)題 const printName = user.printName printName() // 有問(wèn)題
枚舉
在 TypeScript 中使用 enum 關(guān)鍵字創(chuàng)建枚舉,枚舉是一組命名常量,它可以是一組字符串值,也能是一組數(shù)值,也能將兩者混合使用。枚舉分為兩類,分別是常規(guī)枚舉和常量枚舉。
常規(guī)枚舉
常規(guī)枚舉會(huì)作為普通的 JavaScript 對(duì)象注入到編譯后的 JavaScript 代碼中,在源代碼中訪問(wèn)常規(guī)枚舉的成員,將在輸出代碼中轉(zhuǎn)換成訪問(wèn)對(duì)象的屬性。下面的代碼定義了一個(gè)常規(guī)枚舉:
enum Tab { one, two } console.log(Tab) // 打印對(duì)象
常量枚舉
聲明枚舉時(shí),將 const 關(guān)鍵字放在 enum 之前,就能聲明一個(gè)常量枚舉。常量枚舉不會(huì)作為 JavaScript 對(duì)象注入到編譯后的 JavaScript 代碼中,這使產(chǎn)生的 JavaScript 代碼更少,在源代碼中訪問(wèn)常量枚舉的成員,將在輸出代碼中轉(zhuǎn)換為訪問(wèn)枚舉成員的字面量。下面的代碼定義了一個(gè)常量枚舉:
const enum Tab { one, two } console.log(Tab) // ts 程序報(bào)錯(cuò) console.log(Tab.one) // 在 js 代碼中被轉(zhuǎn)換為:console.log(0 /* one */);
常量枚舉比常規(guī)枚舉產(chǎn)生的代碼量更少,它能減少程序的開(kāi)銷,但是常量枚舉的使用范圍更小,它只能在屬性、索引訪問(wèn)表達(dá)式、模塊導(dǎo)入/導(dǎo)出或類型注釋中使用。
枚舉類型
當(dāng)我們定義一個(gè)枚舉時(shí),TypeScript 也將定義一個(gè)同名的類型,這個(gè)類型稱為枚舉類型,用此類型注釋的變量必須引用此枚舉的成員。由于 TypeScript 類型系統(tǒng)是一個(gè)結(jié)構(gòu)化的類型系統(tǒng),所以,除了可以將枚舉成員賦給枚舉類型的變量之外,還能將枚舉的成員的字面量賦值給枚舉類型的變量。代碼如下所示:
interface Page { name: string; tabIndex: Tab; } const page: Page = { name: '首頁(yè)', tabIndex: Tab.two // 將枚舉成員賦給枚舉類型的變量 } page.tabIndex = 0 // 將數(shù)值字面量賦給枚舉類型的變量,不推薦!!!
枚舉的成員類型
枚舉類型是一個(gè)集合類型,枚舉成員有它們的類型。如果變量的類型是枚舉的成員類型,那么不能將枚舉中的其他成員賦給該變量。代碼如下:
let index: Tab.one = Tab.one; index = Tab.two; // ts 程序報(bào)錯(cuò)
枚舉的成員
可以顯式地為枚舉成員設(shè)置數(shù)字或者字符串值,那些沒(méi)有顯式提供值的成員將通過(guò)查看前一個(gè)成員的值自動(dòng)遞增,如果前一個(gè)成員的值不是數(shù)值就會(huì)報(bào)錯(cuò),枚舉成員的值從0開(kāi)始計(jì)數(shù)。TypeScript 將枚舉的成員根據(jù)它的初始化時(shí)機(jī)分為兩大類,分別為:常量成員與計(jì)算成員。
常量成員
如果枚舉成員的值在編譯階段就能確定,這個(gè)成員是常量成員。通過(guò)如下的幾種方式初始化能在編譯階段確定值:
- 不顯式初始化,并且前一個(gè)成員是number類型
- 用數(shù)字或者字符串字面量
- 用前面定義的枚舉常量成員
- 將+、-、~這些一元運(yùn)算符用于枚舉常量成員
- 將+, -, *, /, %, <<, >>, >>>, &, |, ^這些二進(jìn)制操作用于枚舉常量成員
定義枚舉常量成員的代碼如下:
enum MyEnum { one, two = Tab.two, three = -two, four = two + 3, five = four << 4 }
計(jì)算成員
如果枚舉成員的值在運(yùn)行階段才能確定,這個(gè)成員就是計(jì)算成員。代碼如下所示:
enum computedMember { one = Math.random(), two = one + 2 }
補(bǔ)充:計(jì)算成員不能位于常量枚舉(即:const 枚舉)中。在包含字符串成員的枚舉中,枚舉成員不能用表達(dá)式去初始化。
字面量類型
字面量類型就是將一個(gè)特定的字面量作為類型去注釋變量的類型,字面量類型可以是:字符串字面量,數(shù)值字面量和布爾值字面量。用 const 聲明變量,并且不給這個(gè)變量設(shè)置數(shù)據(jù)類型,而是將一個(gè)具體的字符串、數(shù)值或者布爾值賦給它,TypeScript 會(huì)給變量隱式的注釋字面量類型。代碼如下所示:
const type = 'one' // 等同于 const type: 'one' = 'one' // 只能將 Bella 賦值給變量 hello let hello: 'Bella' = 'Bella' hello = 'one' // 類型錯(cuò)誤 // 這個(gè)函數(shù)的返回值只能是true,它的第二個(gè)參數(shù)要么沒(méi)有,要么為 3 function compare(one: string, two?: 3): true { console.log(one, two) return true }
聯(lián)合類型
用管道(|)操作符將一個(gè)或者一個(gè)以上的數(shù)據(jù)類型組合在一起會(huì)形成一個(gè)新的數(shù)據(jù)類型,這個(gè)新的數(shù)據(jù)類型就是聯(lián)合類型,這一種邏輯或??梢詮乃械念愋蛣?chuàng)建聯(lián)合類型,比如:接口,數(shù)值,字符串等。在 TypeScript 中只允許使用聯(lián)合類型中每個(gè)成員類型都存在的屬性或者方法,否則,程序會(huì)報(bào)錯(cuò)。
聯(lián)合類型的用法如下:
// 能將字符串和數(shù)值類型賦值給變量 type let type: string|number = 1 type = '1' // 能將 0、1或布爾值賦值給變量 result let result: 0 | 1 | boolean = true result = 2 // 類型錯(cuò)誤 interface User { name: string } interface Student extends User{ grade: number; } function printInfo(person: User|Student) { // 在這里會(huì)有類型錯(cuò)誤,因?yàn)?grade 屬性只存在 Student類型中 console.log(person.name + ':' + person.grade) }
提示:任何類型與any類型進(jìn)行聯(lián)合操作得到的新類型是 any 類型,任何非 never 類型與 never 類型進(jìn)行聯(lián)合操作得到的新類型是非 never 類型。
交叉類型
在 TypeScript 中,用 & 操作符連接兩個(gè)類型,它會(huì)返回一個(gè)新的類型,這個(gè)新類型被稱為交叉類型,它包含了兩種類型中的屬性,能與這兩種類型中的任何一種兼容。交叉類型相當(dāng)于將兩個(gè)類型的屬性合在一起形成一個(gè)新類型。
當(dāng)兩個(gè)接口 交叉時(shí),這兩個(gè)接口中的公共屬性也會(huì)交叉,接口 交叉與接口擴(kuò)展有些類似,不同點(diǎn)是:如果擴(kuò)展的接口中存在同名但是不兼容的屬性,那么不能進(jìn)行接口擴(kuò)展,但是能夠進(jìn)行接口 交叉,如代碼清單15所示。
代碼清單15
interface User { name: string; age: number } interface Student { name: string; age: string; grade: number; } // 不能進(jìn)行接口擴(kuò)展,因?yàn)?User 和 Student 中的 age 屬性不兼容 interface TypeFromExtends extends User, Student {} // 能夠進(jìn)行接口 交叉 type TypeFromIntersection = User & Student
User 中的 age 是 number 類型,Student 中的 age 是 string 類型,User & Student 會(huì)導(dǎo)致 number & string,由于不存在一個(gè)值既是數(shù)值又是字符串,所以 number & string 返回的類型為 never。代碼清單15中 TypeFromIntersection 的形狀如下所示:
interface TypeFromIntersection { name: string; grade: number; age: never }
提示:任何類型與 any 類型進(jìn)行交叉操作得到的新類型是 any 類型,任何類型與 never 類型交叉操作得到的新類型是 never 類型。
泛型
泛型是指泛型類型,只存在于編譯階段,使用泛型能創(chuàng)建出可組合的動(dòng)態(tài)類型,這提高了類型的可重用性。泛型有一個(gè)存儲(chǔ)類型的變量,也可以將它稱為類型參數(shù),能在其他地方用它注釋變量的類型。泛型可用在函數(shù)、接口、類等類型中,代碼清單16是一個(gè)使用泛型的簡(jiǎn)單示例。
代碼清單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 函數(shù)中的 T 是類型參數(shù),在 lineA 調(diào)用 genericFunc 函數(shù),T 是 string 類型,在 lineB 調(diào)用 genericFunc 函數(shù),T 是 number 類型。
泛型函數(shù)
代碼清單16中的 genericFunc 函數(shù)是一個(gè)泛型函數(shù),它的函數(shù)類型為:<T>(a: T) => T
。genericFunc 函數(shù)只有一個(gè)類型參數(shù),實(shí)際上它可以用多個(gè)類型參數(shù),并且參數(shù)名可以是任何合法的變量名,修改代碼清單16使 genericFunc 有兩個(gè)類型參數(shù),修改結(jié)果如下:
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 的函數(shù)調(diào)用沒(méi)有給類型參數(shù)傳值,但它能夠工作,這是因?yàn)?TypeScript 能推導(dǎo)出T為 number,U 為 string。
泛型接口
在前面介紹過(guò)可以用接口類型描述函數(shù),實(shí)際上也能在接口中使用泛型語(yǔ)法描述泛型函數(shù)。示例代碼如下:
interface genericFunc { <T>(a: T): T }
上述代碼定義的 genericFunc 接口與代碼清單16中的 genericFunc 函數(shù)類型一樣。
在 TypeScript 中,接口類型用于限制對(duì)象的形狀,對(duì)象可能有多個(gè)屬性,可以用接口的類型參數(shù)去注釋這些屬性的數(shù)據(jù)類型。下面的示例將類型參數(shù)提升到接口名的后面,使得接口中的每個(gè)成員都能引用它。
interface genericInterface<T> { a: T, getA: () => T } // 給接口傳遞類型變量 const myObj: genericInterface<number> = { // lineA a: 2, getA: () => { return 2 } }
上述代碼,當(dāng)在 lineA 使用 genericInterface 泛型接口時(shí),將 number 類型傳遞給了 T,所以 myObj 的 a 屬性必須是 number 類型,并且 getA 方法的返回值的類型也必須是 number 類型。
接口可以有類型參數(shù),接口中的函數(shù)字段也能有自己的類型參數(shù),示例代碼如下:
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
上述代碼中類型參數(shù)T是接口的類型參數(shù),在使用接口的時(shí)候就要傳,參數(shù)類型 U 是 printInfo 方法的類型參數(shù),在調(diào)用 printInfo 方法的時(shí)候傳,U 與 T 不同的是,U 只能在 printInfo 函數(shù)中使用,而 T 可以在接口的所有成員上使用。
泛型類
泛型類與泛型接口類似,它也將類型參數(shù)放在類名的后面,在類的所有實(shí)例字段中都能使用類的類型參數(shù),但在靜態(tài)字段中不能使用類的類型參數(shù)。示例代碼如下:
class Information<T, U> { detail: T; title: U; constructor(detail: T, title: U) { this.detail = detail this.title = title } } // 在實(shí)例化類的時(shí)候?qū)㈩愋蛡鹘o類型參數(shù) new Information<string, string>('detail', 'title')
當(dāng)泛型類存在繼承關(guān)系時(shí),父類的類型參數(shù)通過(guò)子類傳遞給它。示例代碼如下:
class SubClass<C, K> extends Information<C, K> {/* do something*/} new SubClass<number, string>(2,3)
上述代碼在實(shí)例化 SubClass 時(shí),將 number 傳給了 C,將 string 傳給了 K,然后 C 和 K 又傳給 Information。
補(bǔ)充:定義泛型函數(shù),泛型接口和泛型類的語(yǔ)法或多或少存在差異,但有一個(gè)共同點(diǎn)是,它們的類型參數(shù)是在使用泛型的時(shí)候傳,而非在定義泛型的時(shí)候傳,這使泛型具有動(dòng)態(tài)性,提高了類型的可重用性。
在工廠函數(shù)中使用泛型
類類型由靜態(tài)端類型和實(shí)例端類型兩部分組成,現(xiàn)在將泛型運(yùn)用到工廠函數(shù)中,讓它接受任何類作為參數(shù),并返回該類的實(shí)例。示例代碼如下:
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的類型參數(shù)T必須是類的實(shí)例端類型,通過(guò)類名就能引用到類的實(shí)例端類型,所以在lineA和lineB分別將User和Tools傳給了T。
泛型約束
extends 關(guān)鍵字可用于接口擴(kuò)展和類擴(kuò)展,這個(gè)關(guān)鍵字也能用于約束泛型類型參數(shù)的值,比如:<T extends User,K>
,這意味著T的值必須擴(kuò)展自 User 類型,而 K 的值可以是任何合法的類型。下面是用 extends 關(guān)鍵字進(jìn)行類型約束的示例代碼:
interface User { name: string } interface Student extends User { age: number } const ci = { name: 'Ci', age: 2 } // T 的值必須擴(kuò)展自 User 類型,K 的值可以是任何類型 function print<T extends User, K>(user: T, b: K): void{/**do somethine */} print<Student, string>(ci, '3') // 沒(méi)毛病 print<string, string>('tt', 'kk') // 不滿足泛型約束,因?yàn)?string不是擴(kuò)展自 User
上述代碼中的類型參數(shù)T擴(kuò)展自User接口,實(shí)際上泛型類型參數(shù)能擴(kuò)展自任何合法的 TypeScript 類型,比如:字符串,聯(lián)合類型,函數(shù)類型等。
補(bǔ)充:如果泛型約束是:<T extends string | number>
,那么T的值可以是任何字符串或者數(shù)值。
在泛型約束中使用類型參數(shù)
在前面的內(nèi)容中介紹過(guò),類型參數(shù)是一個(gè)變量,可以用它注釋其他變量的類型,它也能約束其他類型參數(shù)。用法如下:
function callFunc<T extends FuncType<U>, U>(func: T, arg: U): void interface myInterface<C, K extends keyof C>
上述代碼中的 callFunc 有兩個(gè)類型參數(shù),分別是T和U,U可以是任何類型,T 必須擴(kuò)展自 FuncType<U>
,FuncType<U>
中的 U 指向 callFunc 的類型參數(shù) U。myInterface 也有兩個(gè)類型參數(shù),分別是 C 和K ,K 擴(kuò)展自 keyof C,keyof C 中 C 指向 myInterface 的類型參數(shù) C。
在泛型中使用條件類型
條件類型的語(yǔ)法為:Type1 extends Type2 ? TrueType: FalseType
,如果 Type1 擴(kuò)展自 Type2,那么表達(dá)式將得到 TrueType,否則得到 FalseType,這個(gè)表達(dá)式只存在于編譯階段,并且只用于類型注釋和類型別名。下面是一個(gè)將條件類型與泛型配合使用的示例:
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 接受一個(gè)類型參數(shù)T,在編譯階段 TypeScript 會(huì)根據(jù) T 是否繼承自 string,去動(dòng)態(tài)的計(jì)算出 MyType 的數(shù)據(jù)類型。如果 T 是聯(lián)合類型,那么會(huì)判斷聯(lián)合類型中的每一個(gè)成員類型是否擴(kuò)展自 string,所以最后一行中的 unionArr 類型為 never[] | string[]。
以上就是TypeScript類型系統(tǒng)自定義數(shù)據(jù)類型教程示例的詳細(xì)內(nèi)容,更多關(guān)于TypeScript自定義數(shù)據(jù)類型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
滾動(dòng)條響應(yīng)鼠標(biāo)滑輪事件實(shí)現(xiàn)上下滾動(dòng)的js代碼
javascript實(shí)現(xiàn)滾動(dòng)條響應(yīng)鼠標(biāo)滑輪的實(shí)現(xiàn)上下滾動(dòng),示例代碼如下2014-06-06js數(shù)組循環(huán)遍歷數(shù)組內(nèi)所有元素的方法
在js中數(shù)組遍歷最簡(jiǎn)單的辦法就是使用for然后再利用arr.length長(zhǎng)度作為for最大限度值即可解決了,下面我們來(lái)看看一些有用的實(shí)例2014-01-01JavaScript實(shí)現(xiàn)移動(dòng)端短信驗(yàn)證碼流程介紹
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)移動(dòng)端發(fā)送短信驗(yàn)證碼案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-10-10Javascript封裝DOMContentLoaded事件實(shí)例
這篇文章主要介紹了Javascript封裝DOMContentLoaded事件實(shí)例,DOMContentLoaded是FF,Opera 9的特有的Event, 當(dāng)所有DOM解析完以后會(huì)觸發(fā)這個(gè)事件,需要的朋友可以參考下2014-06-06利用BootStrap彈出二級(jí)對(duì)話框的簡(jiǎn)單實(shí)現(xiàn)方法
彈出二級(jí)對(duì)話框,即在對(duì)話框的基礎(chǔ)上再?gòu)棾鲆粋€(gè)對(duì)話框.這篇文章主要介紹了利用BootStrap彈出二級(jí)對(duì)話框的簡(jiǎn)單實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-09-09Layui Table js 模擬選中checkbox的例子
今天小編就為大家分享一篇Layui Table js 模擬選中checkbox的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09JavaScript實(shí)現(xiàn)時(shí)鐘特效
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)時(shí)鐘特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06JavaScript使用prototype原型實(shí)現(xiàn)的封裝繼承多態(tài)示例
這篇文章主要介紹了JavaScript使用prototype原型實(shí)現(xiàn)的封裝繼承多態(tài),涉及javascript prototype與面向?qū)ο蟪绦蛟O(shè)計(jì)相關(guān)操作技巧,需要的朋友可以參考下2018-08-08微信小程序云開(kāi)發(fā)實(shí)現(xiàn)云數(shù)據(jù)庫(kù)讀寫(xiě)權(quán)限
這篇文章主要為大家詳細(xì)介紹了微信小程序云開(kāi)發(fā)實(shí)現(xiàn)云數(shù)據(jù)庫(kù)讀寫(xiě)權(quán)限,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05