目錄
1.類型斷言
語(yǔ)法
?2.類型斷言的用途
2.1?將一個(gè)聯(lián)合類型斷言為其中一個(gè)類型?
?2.2 將一個(gè)父類斷言為更加具體的子類
?2.3?將任何一個(gè)類型斷言為?any
2.4?將?any?斷言為一個(gè)具體的類型
類型斷言的限制
雙重?cái)嘌?/p>
類型斷言 vs 類型轉(zhuǎn)換
類型斷言 vs 類型聲明
類型斷言 vs 泛型
1.類型斷言
類型斷言(Type Assertion)可以用來(lái)手動(dòng)指定一個(gè)值的類型。一般運(yùn)用在聯(lián)合類型里面。
語(yǔ)法
值? as? 類型
或者
<類型>值
在 tsx 語(yǔ)法(React 的 jsx 語(yǔ)法的 ts 版)中必須使用前者,即?值 as 類型
。
形如?<Foo>
?的語(yǔ)法在 tsx 中表示的是一個(gè)?ReactNode
,在 ts 中除了表示類型斷言之外,也可能是表示一個(gè)泛型。
故建議大家在使用類型斷言時(shí),統(tǒng)一使用?值 as 類型
?這樣的語(yǔ)法,本書(shū)中也會(huì)貫徹這一思想。
?2.類型斷言的用途
2.1?將一個(gè)聯(lián)合類型斷言為其中一個(gè)類型?
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候,我們只能訪問(wèn)此聯(lián)合類型的所有類型中共有的屬性或方法:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function getName(animal: Cat | Fish) {
return animal.name;
}
?而有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問(wèn)其中一個(gè)類型特有的屬性或方法,比如:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') {
return true;
}
return false;
}
// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
// Property 'swim' does not exist on type 'Cat'.
上面的例子中,獲取?animal.swim
?的時(shí)候會(huì)報(bào)錯(cuò)。
此時(shí)可以使用類型斷言,將?animal
?斷言成?Fish
:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
?這樣就可以解決訪問(wèn)?animal.swim
?時(shí)報(bào)錯(cuò)的問(wèn)題了。
需要注意的是,類型斷言只能夠「欺騙」TypeScript 編譯器,無(wú)法避免運(yùn)行時(shí)的錯(cuò)誤,反而濫用類型斷言可能會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
上面的例子編譯時(shí)不會(huì)報(bào)錯(cuò),但在運(yùn)行時(shí)會(huì)報(bào)錯(cuò):
Uncaught TypeError: animal.swim is not a function~?
原因是?(animal as Fish).swim()
?這段代碼隱藏了?animal
?可能為?Cat
?的情況,將?animal
?直接斷言為?Fish
?了,而 TypeScript 編譯器信任了我們的斷言,故在調(diào)用?swim()
?時(shí)沒(méi)有編譯錯(cuò)誤。
可是?swim
?函數(shù)接受的參數(shù)是?Cat | Fish
,一旦傳入的參數(shù)是?Cat
?類型的變量,由于?Cat
?上沒(méi)有?swim
?方法,就會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤了。
總之,使用類型斷言時(shí)一定要格外小心,盡量避免斷言后調(diào)用方法或引用深層屬性,以減少不必要的運(yùn)行時(shí)錯(cuò)誤。
?2.2 將一個(gè)父類斷言為更加具體的子類
當(dāng)類之間有繼承關(guān)系時(shí),類型斷言也是很常見(jiàn)的:
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
?上面的例子中,我們聲明了函數(shù)?isApiError
,它用來(lái)判斷傳入的參數(shù)是不是?ApiError
?類型,為了實(shí)現(xiàn)這樣一個(gè)函數(shù),它的參數(shù)的類型肯定得是比較抽象的父類?Error
,這樣的話這個(gè)函數(shù)就能接受?Error
?或它的子類作為參數(shù)了。
但是由于父類?Error
?中沒(méi)有?code
?屬性,故直接獲取?error.code
?會(huì)報(bào)錯(cuò),需要使用類型斷言獲取?(error as ApiError).code
。
大家可能會(huì)注意到,在這個(gè)例子中有一個(gè)更合適的方式來(lái)判斷是不是?ApiError
,那就是使用?instanceof
:
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (error instanceof ApiError) {
return true;
}
return false;
}
// index.ts:9:26 - error TS2693: 'ApiError' only refers to a type, but is being used as a value here.
此時(shí)就只能用類型斷言,通過(guò)判斷是否存在?code
?屬性,來(lái)判斷傳入的參數(shù)是不是?ApiError
?了:
interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
?2.3?將任何一個(gè)類型斷言為?any
理想情況下,TypeScript 的類型系統(tǒng)運(yùn)轉(zhuǎn)良好,每個(gè)值的類型都具體而精確。
當(dāng)我們引用一個(gè)在此類型上不存在的屬性或方法時(shí),就會(huì)報(bào)錯(cuò):
const foo: number = 1;
foo.length = 1;
// index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'.
?上面的例子中,數(shù)字類型的變量?foo
?上是沒(méi)有?length
?屬性的,故 TypeScript 給出了相應(yīng)的錯(cuò)誤提示。
這種錯(cuò)誤提示顯然是非常有用的。
但有的時(shí)候,我們非常確定這段代碼不會(huì)出錯(cuò),比如下面這個(gè)例子:
window.foo = 1;
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
上面的例子中,我們需要將?window
?上添加一個(gè)屬性?foo
,但 TypeScript 編譯時(shí)會(huì)報(bào)錯(cuò),提示我們?window
?上不存在?foo
?屬性。
此時(shí)我們可以使用?as any
?臨時(shí)將?window
?斷言為?any
?類型:
(window as any).foo = 1;
?在?any
?類型的變量上,訪問(wèn)任何屬性都是允許的。
需要注意的是,將一個(gè)變量斷言為?any
?可以說(shuō)是解決 TypeScript 中類型問(wèn)題的最后一個(gè)手段。
它極有可能掩蓋了真正的類型錯(cuò)誤,所以如果不是非常確定,就不要使用?as any
。
上面的例子中,我們也可以通過(guò)[擴(kuò)展 window 的類型(TODO)][]解決這個(gè)錯(cuò)誤,不過(guò)如果只是臨時(shí)的增加?foo
?屬性,as any
?會(huì)更加方便。
總之,一方面不能濫用?as any
,另一方面也不要完全否定它的作用,我們需要在類型的嚴(yán)格性和開(kāi)發(fā)的便利性之間掌握平衡(這也是?TypeScript 的設(shè)計(jì)理念之一),才能發(fā)揮出 TypeScript 最大的價(jià)值。
2.4?將?any?斷言為一個(gè)具體的類型
在日常的開(kāi)發(fā)中,我們不可避免的需要處理?any
?類型的變量,它們可能是由于第三方庫(kù)未能定義好自己的類型,也有可能是歷史遺留的或其他人編寫(xiě)的爛代碼,還可能是受到 TypeScript 類型系統(tǒng)的限制而無(wú)法精確定義類型的場(chǎng)景。
遇到?any
?類型的變量時(shí),我們可以選擇無(wú)視它,任由它滋生更多的?any
。
我們也可以選擇改進(jìn)它,通過(guò)類型斷言及時(shí)的把?any
?斷言為精確的類型,亡羊補(bǔ)牢,使我們的代碼向著高可維護(hù)性的目標(biāo)發(fā)展。
舉例來(lái)說(shuō),歷史遺留的代碼中有個(gè)?getCacheData
,它的返回值是?any
:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
那么我們?cè)谑褂盟鼤r(shí),最好能夠?qū)⒄{(diào)用了它之后的返回值斷言成一個(gè)精確的類型,這樣就方便了后續(xù)的操作:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
?上面的例子中,我們調(diào)用完?getCacheData
?之后,立即將它斷言為?Cat
?類型。這樣的話明確了?tom
?的類型,后續(xù)對(duì)?tom
?的訪問(wèn)時(shí)就有了代碼補(bǔ)全,提高了代碼的可維護(hù)性。
類型斷言的限制
從上面的例子中,我們可以總結(jié)出:
- 聯(lián)合類型可以被斷言為其中一個(gè)類型
- 父類可以被斷言為子類
- 任何類型都可以被斷言為 any
- any 可以被斷言為任何類型
那么類型斷言有沒(méi)有什么限制呢?是不是任何一個(gè)類型都可以被斷言為任何另一個(gè)類型呢?
答案是否定的——并不是任何一個(gè)類型都可以被斷言為任何另一個(gè)類型。
具體來(lái)說(shuō),若?A
?兼容?B
,那么?A
?能夠被斷言為?B
,B
?也能被斷言為?A
。
下面我們通過(guò)一個(gè)簡(jiǎn)化的例子,來(lái)理解類型斷言的限制:
interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
let tom: Cat = {
name: 'Tom',
run: () => { console.log('run') }
};
let animal: Animal = tom;
?我們知道,TypeScript 是結(jié)構(gòu)類型系統(tǒng),類型之間的對(duì)比只會(huì)比較它們最終的結(jié)構(gòu),而會(huì)忽略它們定義時(shí)的關(guān)系。
在上面的例子中,Cat
?包含了?Animal
?中的所有屬性,除此之外,它還有一個(gè)額外的方法?run
。TypeScript 并不關(guān)心?Cat
?和?Animal
?之間定義時(shí)是什么關(guān)系,而只會(huì)看它們最終的結(jié)構(gòu)有什么關(guān)系——所以它與?Cat extends Animal
?是等價(jià)的:
interface Animal {
name: string;
}
interface Cat extends Animal {
run(): void;
}
那么也不難理解為什么?Cat
?類型的?tom
?可以賦值給?Animal
?類型的?animal
?了——就像面向?qū)ο缶幊讨形覀兛梢詫⒆宇惖膶?shí)例賦值給類型為父類的變量。
我們把它換成 TypeScript 中更專業(yè)的說(shuō)法,即:Animal
?兼容?Cat
。
雙重?cái)嘌?/h3>
既然:
- 任何類型都可以被斷言為 any
- any 可以被斷言為任何類型
那么我們是不是可以使用雙重?cái)嘌?as any as Foo
?來(lái)將任何一個(gè)類型斷言為任何另一個(gè)類型呢?
interface Cat {
run(): void;
}
interface Fish {
swim(): void;
}
function testCat(cat: Cat) {
return (cat as any as Fish);
}
?在上面的例子中,若直接使用?cat as Fish
?肯定會(huì)報(bào)錯(cuò),因?yàn)?Cat
?和?Fish
?互相都不兼容。
但是若使用雙重?cái)嘌裕瑒t可以打破「要使得?A
?能夠被斷言為?B
,只需要?A
?兼容?B
?或?B
?兼容?A
?即可」的限制,將任何一個(gè)類型斷言為任何另一個(gè)類型。
若你使用了這種雙重?cái)嘌裕敲词邪司攀欠浅ee(cuò)誤的,它很可能會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。
除非迫不得已,千萬(wàn)別用雙重?cái)嘌浴?/strong>
類型斷言 vs 類型轉(zhuǎn)換
類型斷言只會(huì)影響 TypeScript 編譯時(shí)的類型,類型斷言語(yǔ)句在編譯結(jié)果中會(huì)被刪除:
function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值為 1
在上面的例子中,將?something
?斷言為?boolean
?雖然可以通過(guò)編譯,但是并沒(méi)有什么用,代碼在編譯后會(huì)變成:
function toBoolean(something) {
return something;
}
toBoolean(1);
// 返回值為 1
所以類型斷言不是類型轉(zhuǎn)換,它不會(huì)真的影響到變量的類型。
若要進(jìn)行類型轉(zhuǎn)換,需要直接調(diào)用類型轉(zhuǎn)換的方法:
function toBoolean(something: any): boolean {
return Boolean(something);
}
toBoolean(1);
// 返回值為 true
類型斷言 vs 類型聲明
在這個(gè)例子中:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
?我們使用?as Cat
?將?any
?類型斷言為了?Cat
?類型。
但實(shí)際上還有其他方式可以解決這個(gè)問(wèn)題:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom: Cat = getCacheData('tom');
tom.run();
上面的例子中,我們通過(guò)類型聲明的方式,將?tom
?聲明為?Cat
,然后再將?any
?類型的?getCacheData('tom')
?賦值給?Cat
?類型的?tom
。
這和類型斷言是非常相似的,而且產(chǎn)生的結(jié)果也幾乎是一樣的——tom
?在接下來(lái)的代碼中都變成了?Cat
?類型。
類型斷言 vs 泛型
還是這個(gè)例子:
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
我們還有第三種方式可以解決這個(gè)問(wèn)題,那就是泛型:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-440209.html
function getCacheData<T>(key: string): T {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData<Cat>('tom');
tom.run();
通過(guò)給?getCacheData
?函數(shù)添加了一個(gè)泛型?<T>
,我們可以更加規(guī)范的實(shí)現(xiàn)對(duì)?getCacheData
?返回值的約束,這也同時(shí)去除掉了代碼中的?any
,是最優(yōu)的一個(gè)解決方案。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-440209.html
到了這里,關(guān)于typeScript中的類型斷言和類型別名、字符串字面量類型的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!