ts入門了一段時間,但是碰上一些高級類型老是一頭霧水,還須要去看文檔,因此總結了一些工做中常常用到的一些高級類型。因此閱讀以前你須要掌握一點兒ts基礎。typescript
交叉類型將多個類型合併爲一個新的類型,新的具備全部參與合併的類型的特性,本質上是一種並的操做。形式以下:安全
T & U
//例子:
function extend<T , U>(first: T, second: U) : T & U {
let result: <T & U> = {}
for (let key in first) {
result[key] = first[key]
}
for (let key in second) {
if(!result.hasOwnProperty(key)) {
result[key] = second[key]
}
}
return result
}
複製代碼
聯合類型用 | 分割每個類型,表示只能取其中的一種類型,好比: number | string | boolean 的類型只能是這三個的一種,不能共存。 若是一個值的類型是聯合類型,那麼咱們只能訪問它們中共有的屬性或者方法,本質上是一種交的關係,好比:bash
interface Cat {
eat(food) : void
miao() : string
}
interface Dog {
eat(food) : void
wang() : string
}
function getPet() : Cat | Dog {
...
}
let pet = getPet()
pet.eat() //正確
pet.miao() //報錯
複製代碼
聯合類型讓一個值能夠爲不一樣的類型,但隨之帶來的問題就是訪問非共同方法時會報錯。那麼該如何區分值的具體類型,以及如何訪問共有成員?函數
let pet = getPet()
if((<Cat>pet).miao) {
(<Cat>pet).miao()
} else {
(<Dog>pet).wang()
}
複製代碼
可見使用類型斷言就不得不寫一堆蹩腳的尖括號,那麼尚未更簡潔明瞭辦法來判斷類型呢?ui
function isCat(pet: Cat | Dog) : pet is Cat {
return (<Cat>pet).miao !== undefined
}
複製代碼
這種param is SomeType 的形式就是類型保護,咱們能夠用它來明確一個聯合類型變量的具體形式,在調用時ts就會將變量縮減爲該具體類型,調用就是合法的了this
if (isCat(pet)) {
pet.miao() //正確
} else {
pet.wang()
}
複製代碼
容許這麼作是由於:typescript可以經過類型保護知道if語句裏的pet類型必定是Fish類型,並且else語句裏的pet類型必定不是Fish類型,那麼就是Bird類型了spa
當咱們使用了typeof和instanceof後,typescript就會自動限制類型爲某一具體類型,從而咱們能夠安全地在語句體內使用具體類型的方法和屬性,如:prototype
function show(param: number | string) {
if (typeof param === 'number') {
console.log(`${param} is number`)
} else {
console.log(`${param} is string`)
}
}
複製代碼
可是typeof只支持number、string、boolean或者symbol(只有這些狀況下能夠被認爲是類型保護)code
若是是類,咱們能夠用instanceof對象
let an = getSomeKindType()
if (an instanceof Animal) {
an // 此時aa會細化爲Animal類型
}
if (an instanceof People) {
an //此時aa會細化爲People類型
}
複製代碼
ts要求instanceof右側是一個構造函數,而ts會將其細化爲:
null和undefined能夠賦給任何的類型,由於它們是全部其餘類型的一個有效值,如:
// 如下語句均合法
let x1: number = null
let x2: string = null
let x3: boolean = null
let x4: undefined = null
let y1: number = undefined
let y2: string = undefined
let y3: boolean = undefined
let y4: null = undefined
複製代碼
在typescript裏,咱們可使用--strictNullChecks標記,開啓這個標記後,當咱們聲明一個變量時,就不會自動包含null或undefined,可是咱們能夠手動使用聯合類型來明確包含,如:
let x = 123
x = null // 報錯
let y: number | null = 123
y = null // 容許
y = undefined // 報錯,`undefined`不能賦值給`number | null`
複製代碼
當開啓了--strictNullChecks,可選參數/屬性就會被自動地加上| undefined,如:
function foo(x: number, y?: number) {
return x + (y || 0)
}
foo(1, 2) // 容許
foo(1) // 容許
foo(1, undefined) // 容許
foo(1, null) // 報錯,不容許將null賦值給`number | undefined`類型
複製代碼
類型別名能夠給現有的類型起個新名字,它和接口很像但又不同,由於類型別名能夠做用於原始值、聯合類型、元組及其餘任何須要手寫的類型,語法如:
type Name = string
別名不會新建一個類型,它只會建立一個新的名字來引用現有類型。因此在VSCode裏將鼠標放在別名上時,顯示的是所引用的那個類型
type Container<T> = {
value: T
}
let name: Container<string> = {
value: 'hello'
}
複製代碼
字符串字面量類型容許咱們定義一個別名,類型爲別名的變量只能取固定的幾個值,如:
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
let x1: Easing = 'uneasy' // 報錯: Type '"uneasy"' is not assignable to type 'Easing'
let x2: Easing = 'ease-in' // 容許
複製代碼
能夠合併字符串字面量類型、聯合類型、類型保護和類型別名來建立可辨識聯合的高級模式(也稱爲標籤聯合或者代數數據類型),具備3個要素:
建立一個可辨識聯合類型,首先須要聲明將要聯合的接口,每一個接口都要有一個可辨識的特徵,如(kind屬性):
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
複製代碼
如今,各個接口之間仍是沒有關聯的,因此咱們須要使用類型別名來聯合這幾個接口,如:
type Shape = Square | Rectangle | Circle
複製代碼
如今,使用可辨識聯合,如:
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size
case 'rectangle':
return s.height * s.width
case 'circle':
return Math.PI * s.radius ** 2
}
}
複製代碼
多態的this類型表示的是某個包含類或接口的子類型,例子如:
class BasicCalculator {
public constructor(protected value: number = 0) {
}
public currentValue(): number {
return this.value
}
public add(operand: number): this {
this.value += operand
return this
}
public multiply(operand: number): this {
this.value *= operand
return this
}
}
let v = new BasicCalculator(2).multiply(5).add(1).currentValue() // 11
複製代碼
因爲使用了this類型,當子類繼承父類的時候,新的類就能夠直接使用以前的方法,而不須要作任何的改變,如:
class ScientificCalculator extends BasicCalculator {
public cconstructor(value = 0) {
super(value)
}
public sin() {
this.value = Math.sin(this.value)
return this
}
}
let v = new BasicCalculator(2).multiply(5).sin().add(1).currentValue()
複製代碼
若是沒有this類型,那麼ScientificCalculator就不可以在繼承BasicCalculator的同時還保持接口的連貫性。由於multiply方法會返回BasicCalculator類型,而BasicCalculator沒有sin方法。然而,使用this類型,multiply就會返回this,在這裏就是ScientificCalculator
索引類型能使編譯器可以檢查使用了動態屬性名的代碼,好比咱們想要寫一個函數,它能夠選取對象中的部分元素的值,那麼:
function pluck<T, K entends keyof T>(o: T, names: K[]) : T[K][] {
return names.map(n => o[n])
}
interface Person {
name: string
age: number
}
let p: Person = {
name: 'LL',
age: 18
}
let res = pluck(p, ['name']) //容許
複製代碼
以上代碼解釋以下:
因此,根據以上例子,觸類旁通有:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
let obj = {
name: 'LL',
age: 18,
male: true
}
let x1 = getProperty(obj, 'name') // 容許,x1的類型爲string
let x2 = getProperty(obj, 'age') // 容許,x2的類型爲number
let x3 = getProperty(obj, 'male') // 容許,x3的類型爲boolean
let x4 = getProperty(obj, 'hobby') // 報錯:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.
複製代碼
keyof和T[K]與字符串索引簽名進行交互,若是有一個帶有字符串索引簽名的類型,那麼keyof T爲string,且T[string]爲索引簽名的類型,如:
interface Demo<T> {
[key: string]: T
}
let keys: keyof Demo<boolean> // keys的類型爲string
let value: Demo<number>['foo'] // value的類型爲number
複製代碼
咱們可能會遇到這麼一些需求: 1)將一個現有類型的每一個屬性都變爲可選的,如:
interface Person {
name: string
age: number
}
複製代碼
可選版本爲:
interface PersonPartial {
name?: string
age?: number
}
複製代碼
2)或者將每一個屬性都變爲只讀的,如:
interface PersonReadonly {
readonly name: string
readonly age: number
}
複製代碼
而如今typescript爲咱們提供了映射類型,可以使得這種轉化更加方便,在映射類型裏,新類型將以相同的形式去轉換舊類型裏每一個屬性,如以上例子能夠改寫爲:
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}//ts 源碼
type Partial<T> = {
[P in keyof T]?: T[P]
}//ts源碼
type PersonReadonly = Readonly<Person>
type PersonPartial = Partial<Person>
複製代碼
另外ts還給咱們提供了Required,Pick,Record,幾種經常使用到的類型,下面是它們的源碼:
type Required<T> = {
[P in keyof T]-?: T[P];
}
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
type Record<K extends keyof any, T> = {
[P in K]: T;
}
type personRequired = Required<Person>;
// personRequired === {name: string; age: number}
type personPick = Pick<Person, "name">;
// person5 === {name: string}
type personRecord = Record<'name' | 'age', string>
// personRecord === {name: string; age: string}
複製代碼
咱們還能夠寫出更多的通用映射類型,如:
// 可爲空類型
type Nullable<T> {
[P in keyof T]: T[P] | null
}
// 包裝一個類型的屬性
type Proxy<T> = {
get(): T
set(value: T): void
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
// ...
}
let proxyProps = proxify(props)
複製代碼
上面展現瞭如何包裝一個類型,那麼與之相反的就有拆包操做,示例如:
function unproxify<T>(t: Proxify<T>): T {
let result = <T>{}
for (const k in t) {
result[k] = t[k].get()
}
return result
}
let originalProps = unproxify(proxyProps)
複製代碼