溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

TypeScript如何自定義數(shù)據(jù)類型

發(fā)布時(shí)間:2022-11-15 09:09:47 來源:億速云 閱讀:226 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“TypeScript如何自定義數(shù)據(jù)類型”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“TypeScript如何自定義數(shù)據(jù)類型”文章能幫助大家解決問題。

TypeScript 類型系統(tǒng)和自定義數(shù)據(jù)類型

TypeScript 在 JavaScript 的基礎(chǔ)上增加了靜態(tài)類型系統(tǒng),它使代碼的可讀性更強(qiáng),讓代碼重構(gòu)變得更容易。但是對 TypeScript 而言,它的靜態(tài)類型系統(tǒng)是可選的,這讓JavaScript 程序很容易就能遷移到 TypeScript 程序。

什么是類型系統(tǒng)

類型系統(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)是匹配的,類型就兼容。

函數(shù)類型

在 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 簡潔很多,而且也更加容易理解。

可選參數(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í)候,沒有給 printDetail 傳遞第二個(gè)參數(shù),那么 age 取值為 23。在調(diào)用函數(shù)的時(shí)候如果傳遞的參數(shù)值為 undefined,這相當(dāng)于沒有傳參數(shù)值。

函數(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等。

接口擴(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í)候,可能不確定對象有哪些屬性名,但屬性名對應(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對象都是可索引的,所以能用可索引的接口去注釋它們。

用接口描述函數(shù)

上一節(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)鍵字

使用 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ù)也是對象,它可以有自己的屬性。類的靜態(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)出來,下面用代碼清單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ù)類型為父類的類型。

將 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ù)。下面通過代碼清單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
}

聯(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)?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 類型。

泛型函數(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)用沒有給類型參數(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)性,提高了類型的可重用性。

在工廠函數(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í)例端類型,通過類名就能引用到類的實(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ù)值。

在泛型約束中使用類型參數(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)。

向AI問一下細(xì)節(jié)

免責(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)容。

AI