您好,登錄后才能下訂單哦!
這篇文章主要介紹“TypeScript如何自定義數(shù)據(jù)類型”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“TypeScript如何自定義數(shù)據(jù)類型”文章能幫助大家解決問題。
TypeScript 在 JavaScript 的基礎(chǔ)上增加了靜態(tài)類型系統(tǒng),它使代碼的可讀性更強(qiáng),讓代碼重構(gòu)變得更容易。但是對 TypeScript 而言,它的靜態(tài)類型系統(tǒng)是可選的,這讓JavaScript 程序很容易就能遷移到 TypeScript 程序。
類型系統(tǒng)是一組規(guī)則,它用來規(guī)定編程語言如何將變量、類、函數(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ùn)行階段進(jìn)行類型檢查,所以與類型相關(guān)的錯(cuò)誤要在運(yùn)行階段才會(huì)被暴露出來。
靜態(tài)類型系統(tǒng)
TypeScript 在 JavaScript 的基礎(chǔ)上增加了靜態(tài)類型系統(tǒng),它使 TypeScript 程序在編譯階段就進(jìn)行類型檢查,與類型相關(guān)的錯(cuò)誤在編譯階段就能暴露出來,這使開發(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)是匹配的,類型就兼容。
在 TypeScript 中有多種方式去描述函數(shù)的簽名,例如:函數(shù)類型表達(dá)式、接口類型。在這里先介紹如何用函數(shù)類型表達(dá)式描述函數(shù)的簽名。函數(shù)類型表達(dá)式語法如下:
// 這是一個(gè)函數(shù)類型,它描述的函數(shù)接受兩個(gè)參數(shù),分別是name和age,name是string類型,age是number類型,這個(gè)函數(shù)沒有返回值 (name: string, age: number) => void // lineA // 這是一個(gè)函數(shù)類型,它描述的函數(shù)接受一個(gè)參數(shù),這個(gè)參數(shù)是number類型,函數(shù)的返回值的類型是 number (a: number) => number
函數(shù)類型表達(dá)式語法與 ES2015 的箭頭函數(shù)語法很相似,但是函數(shù)類型表達(dá)式不會(huì)創(chuàng)建任何函數(shù),它只存在于 TypeScript 編譯時(shí)。從上述代碼可以看出,函數(shù)的返回值類型放在箭頭符號(hào)(=>)的后面,函數(shù)的參數(shù)類型以 :type 的形式放在參數(shù)的后面。代碼清單1演示了如何使用函數(shù)類型表達(dá)式。
代碼清單1
// 聲明一個(gè)名為 startHandle 的變量,它的數(shù)據(jù)類型是函數(shù),它沒有返回值,它接受一個(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 乍一看不好理解,主要是它太長了,而且存在冗余的部分,可以使用類型別名解決這個(gè)問題。
定義類型別名需要用到的關(guān)鍵字是 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中的函數(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ò)誤
函數(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í)候,沒有給 printDetail 傳遞第二個(gè)參數(shù),那么 age 取值為 23。在調(diào)用函數(shù)的時(shí)候如果傳遞的參數(shù)值為 undefined,這相當(dāng)于沒有傳參數(shù)值。
函數(shù)重載指的是函數(shù)名相同,但是參數(shù)列表不相同。JavaScript 沒有靜態(tài)類型檢查,所以 JavaScript 不支持函數(shù)重載,在 TypeScript 中支持函數(shù)重載,但是 TypeScript 中的函數(shù)重載只存在于它的編譯階段。
在TypeScript中函數(shù)重載的寫法如下:
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ù)沒有函數(shù)體,它們被稱為重載簽名,第3行到第9行的函數(shù)有函數(shù)體,它被稱為實(shí)現(xiàn)簽名。
編寫重載函數(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) // 這是沒問題的 getMonth(new Date()) // 這是沒問題的 getMonth(Math.random() > 0.5 ? 2344553444: new Date()) // 有問題
上述代碼第三行不能在編譯階段確定它調(diào)用的是哪一個(gè)重載,如果你非要這么調(diào)用,那么你不能使用重載函數(shù)。
補(bǔ)充:在 TypeScript 中有一個(gè)通用的函數(shù)類型,那就是 Function,它表示所有的函數(shù)類型。
在 TypeScript 中,接口類型用于限制對象的形狀,即:對象有哪些屬性,以及這些屬性的數(shù)據(jù)類型是什么,在后文將接口類型簡稱為接口。有三種接口類型,分別是隱式接口,命名接口和匿名接口。
隱式接口
當(dāng)創(chuàng)建一個(gè)帶有 key/value 的對象時(shí),TypeScript 會(huì)通過檢查對象的屬性名和每個(gè)屬性值的數(shù)據(jù)類型去創(chuàng)建一個(gè)隱式接口,代碼如下:
const user = { name: 'bella', age: 23 } // TypeScript 創(chuàng)建的隱式接口為: { name: string; Age: number; }
匿名接口
匿名接口沒有名稱,它不能被重復(fù)使用,使用匿名接口會(huì)造成代碼冗余,隱式接口也是匿名接口。用匿名接口限制對象的形狀,代碼如下:
const student: { name: string; age: number } = { name: 'bella', age: 23 } const pig: { name: string; age: number } = { name: 'hua', age: 2 }
命名接口
在 TypeScript 中,使用 interface 關(guān)鍵字定義命名接口,命名接口可以讓代碼更加簡潔,因?yàn)樗梢员恢貜?fù)使用。代碼如下:
// 定義接口類型 interface BaseInfo { name: string; age: number } // 用接口類型注釋對象的類型 const bella: BaseInfo = { name: 'bella', age: 23 } const hua: BaseInfo = { name: 'hua', age: 2 }
在介紹函數(shù)類型的時(shí)候介紹了函數(shù)的可選參數(shù),接口的可選屬性與函數(shù)的可選參數(shù)類似,它指的是,在對象中可以有這個(gè)屬性也可以沒有這個(gè)屬性。接口的可選屬性的格式為:propertyName?: type,即:在屬性名與冒號(hào)之間加一個(gè)問號(hào)。在接口中定義可選屬性,能讓這個(gè)接口適用范圍更廣,但是它會(huì)帶來一些問題,比如:不能用可選屬性參與算術(shù)運(yùn)算。
如果對象的某個(gè)屬性在創(chuàng)建之后不可修改,可以在創(chuàng)建接口的時(shí)候?qū)⑦@個(gè)屬性指定為只讀屬性,接口的只讀屬性的格式為:readonly propertyName: type,即:在屬性名的前面加上 readonly 關(guān)鍵字。對象的只讀屬性不能被單獨(dú)修改,但是可以將整個(gè)對象重復(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 對象重新賦值 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等。
與 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í)候,可能不確定對象有哪些屬性名,但屬性名對應(yīng)的值的數(shù)據(jù)類型是確定的,這種情況可以用帶有索引簽名的接口來解決,用法如代碼清單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 對應(yīng)的代碼就是接口的索引簽名,索引簽名 key 的數(shù)據(jù)類型只能是 string 或者是 number,value 的數(shù)據(jù)類型可以使用任何合法的 TypeScript 類型。用 Car 接口注釋的對象,一定要包含 price 屬性,并且 price 的值是 sting 類型,對象其他的屬性名只需要是字符串,屬性值是 number 類型就能滿足要求。
補(bǔ)充:數(shù)組和純JavaScript對象都是可索引的,所以能用可索引的接口去注釋它們。
上一節(jié)介紹了用函數(shù)類型表達(dá)式描述函數(shù)的簽名,除此之外,接口也能描述函數(shù)的簽名,代碼清單2中的 myFnType 可被改寫成下面這種形式:
interface myFnType { (a: number, b: number): void }
帶有匿名方法簽名的接口可用于描述函數(shù),在 JavaScript 中,函數(shù)也是對象,因此在函數(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)該通過 new 關(guān)鍵字調(diào)用它,但在 JavaScript 領(lǐng)域沒有這種限制,幸運(yùn)的是,在 TypeScript 中,構(gòu)造函數(shù)類型的接口可描述構(gòu)造函數(shù)。將代碼清單9中 Arithmetic 改寫成代碼清單10中的形式,使函數(shù) add 只能通過 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)鍵字讓類實(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ò)。
類的實(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ù)也是對象,它可以有自己的屬性。類的靜態(tài)端類型用于描述構(gòu)造函數(shù)的形狀,包括構(gòu)造函數(shù)的參數(shù)、返回值和它的靜態(tài)成員,:typeof ClassName返回類的靜態(tài)端類型。
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)出來,下面用代碼清單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) // 沒有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 student.setRelationship(otherStudent) // 沒有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 student.printName(user1) // 有類型錯(cuò)誤,此時(shí)printName能接受參數(shù)類型為 Student的類型 user.printName(student) // 沒有類型錯(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ù)類型為父類的類型。
默認(rèn)情況下,函數(shù)中 this 的值取決于函數(shù)的調(diào)用方式,在 TypeScript 中,如果將 this 作為函數(shù)的參數(shù),那么 TypeScript 會(huì)檢查調(diào)用函數(shù)時(shí)是否帶有正確的上下文。this 必須是第一個(gè)參數(shù),并且只存在于編譯階段,在箭頭函數(shù)中不能包含 this 參數(shù)。下面通過代碼清單14加以說明。
代碼清單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() // 沒問題 const printName = user.printName printName() // 有問題
在 TypeScript 中使用 enum 關(guān)鍵字創(chuàng)建枚舉,枚舉是一組命名常量,它可以是一組字符串值,也能是一組數(shù)值,也能將兩者混合使用。枚舉分為兩類,分別是常規(guī)枚舉和常量枚舉。
常規(guī)枚舉
常規(guī)枚舉會(huì)作為普通的 JavaScript 對象注入到編譯后的 JavaScript 代碼中,在源代碼中訪問常規(guī)枚舉的成員,將在輸出代碼中轉(zhuǎn)換成訪問對象的屬性。下面的代碼定義了一個(gè)常規(guī)枚舉:
enum Tab { one, two } console.log(Tab) // 打印對象
常量枚舉
聲明枚舉時(shí),將 const 關(guān)鍵字放在 enum 之前,就能聲明一個(gè)常量枚舉。常量枚舉不會(huì)作為 JavaScript 對象注入到編譯后的 JavaScript 代碼中,這使產(chǎn)生的 JavaScript 代碼更少,在源代碼中訪問常量枚舉的成員,將在輸出代碼中轉(zhuǎ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)生的代碼量更少,它能減少程序的開銷,但是常量枚舉的使用范圍更小,它只能在屬性、索引訪問表達(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: '首頁', tabIndex: Tab.two // 將枚舉成員賦給枚舉類型的變量 } page.tabIndex = 0 // 將數(shù)值字面量賦給枚舉類型的變量,不推薦!!!
枚舉類型是一個(gè)集合類型,枚舉成員有它們的類型。如果變量的類型是枚舉的成員類型,那么不能將枚舉中的其他成員賦給該變量。代碼如下:
let index: Tab.one = Tab.one; index = Tab.two; // ts 程序報(bào)錯(cuò)
可以顯式地為枚舉成員設(shè)置數(shù)字或者字符串值,那些沒有顯式提供值的成員將通過查看前一個(gè)成員的值自動(dòng)遞增,如果前一個(gè)成員的值不是數(shù)值就會(huì)報(bào)錯(cuò),枚舉成員的值從0開始計(jì)數(shù)。TypeScript 將枚舉的成員根據(jù)它的初始化時(shí)機(jī)分為兩大類,分別為:常量成員與計(jì)算成員。
常量成員
如果枚舉成員的值在編譯階段就能確定,這個(gè)成員是常量成員。通過如下的幾種方式初始化能在編譯階段確定值:
不顯式初始化,并且前一個(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ù)要么沒有,要么為 3 function compare(one: string, two?: 3): true { console.log(one, two) return true }
用管道(|)操作符將一個(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)?nbsp;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)?nbsp;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è)使用泛型的簡單示例。
代碼清單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 類型。
代碼清單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)用沒有給類型參數(shù)傳值,但它能夠工作,這是因?yàn)?TypeScript 能推導(dǎo)出T為 number,U 為 string。
在前面介紹過可以用接口類型描述函數(shù),實(shí)際上也能在接口中使用泛型語法描述泛型函數(shù)。示例代碼如下:
interface genericFunc { <T>(a: T): T }
上述代碼定義的 genericFunc 接口與代碼清單16中的 genericFunc 函數(shù)類型一樣。
在 TypeScript 中,接口類型用于限制對象的形狀,對象可能有多個(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ù)通過子類傳遞給它。示例代碼如下:
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ù),泛型接口和泛型類的語法或多或少存在差異,但有一個(gè)共同點(diǎn)是,它們的類型參數(shù)是在使用泛型的時(shí)候傳,而非在定義泛型的時(shí)候傳,這使泛型具有動(dòng)態(tài)性,提高了類型的可重用性。
類類型由靜態(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í)例端類型,通過類名就能引用到類的實(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') // 沒毛病 print<string, string>('tt', 'kk') // 不滿足泛型約束,因?yàn)?nbsp;string不是擴(kuò)展自 User
上述代碼中的類型參數(shù)T擴(kuò)展自User接口,實(shí)際上泛型類型參數(shù)能擴(kuò)展自任何合法的 TypeScript 類型,比如:字符串,聯(lián)合類型,函數(shù)類型等。
補(bǔ)充:如果泛型約束是:<T extends string | number>
,那么T的值可以是任何字符串或者數(shù)值。
在前面的內(nèi)容中介紹過,類型參數(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。
條件類型的語法為: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[]。
關(guān)于“TypeScript如何自定義數(shù)據(jù)類型”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。