在 TypeScript 中,一個變量不會被限制爲單一的類型。若是你但願一個變量的值,能夠有多種類型,那麼就可使用 TypeScript 提供的聯合類型。下面咱們來舉一個聯合類型的例子:typescript
let stringOrBoolean: string | boolean = "Semlinker"; interface Cat { numberOfLives: number; } interface Dog { isAGoodBoy: boolean; } let animal: Cat | Dog;
當咱們使用聯合類型時,咱們必須儘可能把當前值的類型收窄爲當前值的實際類型,而類型保護就是實現類型收窄的一種手段。express
昨晚新建了一個 「重學TypeScript」的微信羣,目前「重學TypeScript」專題已有文章十七篇(基礎、高級、設計模式等),計劃至少三十篇。想加羣的小夥伴,加我我的微信 semlinker,備註重學TS。
A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文檔
類型保護是可執行運行時檢查的一種表達式,用於確保該類型在必定的範圍內。換句話說,類型保護能夠保證一個字符串是一個字符串,儘管它的值也能夠是一個數值。類型保護與特性檢測並非徹底不一樣,其主要思想是嘗試檢測屬性、方法或原型,以肯定如何處理值。目前主要有四種的方式來實現類型保護:編程
1. in 關鍵字segmentfault
interface Admin { name: string; privileges: string[]; } interface Employee { name: string; startDate: Date; } type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }
2. typeof 關鍵字設計模式
function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
typeof
類型保護只支持兩種形式:typeof v === "typename"
和 typeof v !== typename
,"typename"
必須是 "number"
, "string"
, "boolean"
或 "symbol"
。 可是 TypeScript 並不會阻止你與其它字符串比較,語言不會把那些表達式識別爲類型保護。安全
3. instanceof 關鍵字微信
interface Padder { getPaddingString(): string; } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) {} getPaddingString() { return Array(this.numSpaces + 1).join(" "); } } class StringPadder implements Padder { constructor(private value: string) {} getPaddingString() { return this.value; } } let padder: Padder = new SpaceRepeatingPadder(6); if (padder instanceof SpaceRepeatingPadder) { // padder的類型收窄爲 'SpaceRepeatingPadder' }
4. 自定義類型保護的類型謂詞(type predicate)函數式編程
function isNumber(x: any): x is number { return typeof x === "number"; } function isString(x: any): x is string { return typeof x === "string"; }
相信除了類型謂詞外,你們對其它三種方式都很熟悉了,下面咱們來着重介紹一下類型謂詞。函數
在開始介紹類型謂詞前,咱們先來看一個示例:this
interface Vehicle { move: (distance: number) => void; } class Car implements Vehicle { move = (distance: number) => { // Move car… }; turnSteeringWheel = (direction: string) => { // Turn wheel… }; } class VehicleController { vehicle: Vehicle; constructor(vehicle: Vehicle) { this.vehicle = vehicle; } } const car = new Car(); const vehicleController = new VehicleController(car); const { vehicle } = vehicleController; vehicle.turnSteeringWheel('left');
儘管你知道汽車是一輛車,但 VehicleController
已經把它簡化爲一輛基本的汽車。由於 Vehicle 並無 turnSteeringWheel
屬性,因此對於以上代碼,TypeScript 編譯器會提示如下錯誤信息:
Property 'turnSteeringWheel' does not exist on type 'Vehicle'.
對於這個問題,咱們能夠利用 instanceof
關鍵字來確保當前的對象是 Car
汽車類的實例:
if(vehicle instanceof Car) { vehicle.turnSteeringWheel('left'); }
但該方案有必定的限制,即它只對類有效。當判斷的對象不是某個類的實例時就無效了,好比:
const anotherCar = { move: (distance: number) => null, turnSteeringWheel: (direction: string) => null }; const anotherVehicleController = new VehicleController(anotherCar); const { vehicle } = anotherVehicleController; if (vehicle instanceof Car) { vehicle.turnSteeringWheel('left'); console.log("這是一輛車"); } else { console.log("這不是一輛車"); }
儘管 anotherCar
跟前面已經定義的 car
擁有相同的形狀,但它並非 Car
汽車類的實例,所以在這種狀況下,vehicle instanceof Car
表達式返回的結果爲 false。因此以上代碼的輸出結果是:"這不是一輛車"。
儘管 typeof
和 instanceof
這兩個關鍵字在不少狀況下能夠知足類型保護的需求,但在函數式編程的領域它們的功能就受限了。那麼咱們應該如何檢查任何對象的類型的?幸運的是,你能夠建立自定義類型保護。
下面咱們繼續以車輛和汽車的例子爲例,來建立一個自定義類型保護函數 —— isCar
,它的具體實現以下:
function isCar(vehicle: any): vehicle is Car { return (vehicle as Car).turnSteeringWheel !== undefined; }
你能夠傳遞任何值給 isCar 函數,用來判斷它是否是一輛車。isCar 函數與普通函數的最大區別是,該函數的返回類型是 vehicle is Car
,這就是咱們前面所說的 "類型謂詞"。
在 isCar 函數的方法體中,咱們不只要檢查 vehicle
變量是否含有 turnSteeringWheel
屬性,並且還要告訴 TS 編譯器,若是上述邏輯語句的返回結果是 true,那麼當前判斷的 vehicle
變量值的類型是 Car 類型。
如今讓咱們來重構一下前面的條件語句:
// vehicle instanceof Car -> isCar(anotherCar) if (isCar(anotherCar)) { anotherCar.turnSteeringWheel('left'); console.log("這是一輛車"); } else { console.log("這不是一輛車"); }
在重構完成後,咱們再次運行代碼,這時控制檯會輸出 "這是一輛車"。好了,如今問題已經解決了。接下來讓咱們來總結一下自定義類型保護有什麼用?
自定義類型保護的主要特色是:
vehicle is Car
;(vehicle as Car).turnSteeringWheel !== undefined
。對於基本數據類型來講,咱們也能夠自定義類型保護來保證類型安全,好比:
const isNumber = (variableToCheck: any): variableToCheck is number => (variableToCheck as number).toExponential !== undefined; const isString = (variableToCheck: any): variableToCheck is string => (variableToCheck as string).toLowerCase !== undefined;
若是你要檢查的類型不少,那麼爲每種類型建立和維護惟一的類型保護可能會變得很繁瑣。針對這個問題,咱們能夠利用 TypeScript 的另外一個特性 —— 泛型,來解決複用問題:
function isOfType<T>( varToBeChecked: any, propertyToCheckFor: keyof T ): varToBeChecked is T { return (varToBeChecked as T)[propertyToCheckFor] !== undefined; }
在以上代碼中,咱們定義了一個通用的類型保護函數,你能夠在須要的時候使用它來縮窄類型。之前面自定義類型保護的例子來講,咱們就能夠按照如下方式來使用 isOfType
通用的類型保護函數:
// isCar(anotherCar) -> isOfType<Car>(vehicle, 'turnSteeringWheel') if (isOfType<Car>(vehicle, 'turnSteeringWheel')) { anotherCar.turnSteeringWheel('left'); console.log("這是一輛車"); } else { console.log("這不是一輛車"); }
有了 isOfType
通用的類型保護函數以後,你沒必要再爲每一個要檢查的類型編寫惟一的類型保護函數。並且在實際的開發過程當中,只要咱們合理的使用類型保護函數,就可讓咱們的代碼在運行時可以保證類型安全。
本人的全棧修仙之路訂閱號,會按期分享 Angular、TypeScript、Node.js/Java 、Spring 相關文章,歡迎感興趣的小夥伴訂閱哈!