系列文章目錄
引入一:Typescript基礎引入(基礎類型、元組、枚舉)
引入二:Typescript面向對象引入(接口、類、多態(tài)、重寫、抽象類、訪問修飾符)
第一章:Typescript基礎知識(Typescript介紹、搭建TypeScript環(huán)境、基本數(shù)據(jù)類型)
第二章:Typescript常用類型(任意值any、數(shù)組Array、函數(shù)Function、元組Tuple、類型推論、聯(lián)合類型)
第三章:Typescript基礎知識(類型斷言、類型別名、字符串字面量類型、枚舉、交叉類型)
第四章:Typescript基礎知識(類型拓寬、類型縮?。?br> 第五章:TypeScript進階知識之類(類的定義、類的基本使用、類的構造函數(shù)、類的屬性和方法、訪問修飾符、類的繼承、抽象類)
第六章:TypeScript進階知識之接口(接口定義、接口屬性、可索引類型、接口表示函數(shù)類型、額外的屬性檢查、接口繼承、接口與類型別名的區(qū)別)
第七章:TypeScript進階知識之泛型(泛型的定義、為什么要使用泛型、泛型的使用、泛型變量、多個類型參數(shù)、泛型類、泛型接口、泛型參數(shù)默認類型、泛型約束)
一、類型拓寬
1.1 什么是類型拓寬
所有通過 let 或 var 定義的變量、函數(shù)的形參、對象的非只讀屬性
,如果滿足指定了初始值且未添加類型注解
的條件,那么它們推斷出來的類型就是指定的初始值字面量類型拓寬后的類型
,這就是字面量類型拓寬。(第三章文章有提及過一嘴)
下面我們通過字符串字面量的示例來理解一下字面量類型拓寬:
let str = 'this is string'; // 類型是 string
let strFun = (str = 'this is string') => str; // 類型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 類型是 'this is string'
let str2 = specifiedStr; // 類型是 'string'
let strFun2 = (str = specifiedStr) => str; // 類型是 (str?: string) => string;
實際上,除了字面量類型拓寬之外,TypeScript 對某些特定類型值也有類似類型拓寬的設計,比如對 null 和 undefined 的類型進行拓寬,通過 let、var 定義的變量如果滿足未顯式聲明類型注解且被賦予了 null 或 undefined 值,則推斷出這些變量的類型是 any:
let x = null; // 類型拓寬成 any
let y = undefined; // 類型拓寬成 any
注意:在嚴格模式下,一些比較老的版本中(2.0)null 和 undefined 并不會被拓寬成“any”。
1.2 如何控制類型拓寬
- 案例一
我們先來看一個示例,如下代碼所示:
interface Vector {
x: number;
y: number;
z: number;
}
function getComponent(vector: Vector, axis: "x" | "y" | "z") {
return vector[axis];
}
let x = "x";
let vec = { x: 1, y: 2, z: 3 };
getComponent(vec, x); // Error:類型“string”的參數(shù)不能賦給類型“"x" | "y" | "z"”的參數(shù)。
上述代碼中,為什么會出現(xiàn)錯誤呢?通過 TypeScript 的錯誤提示消息,我們知道是因為變量 x 的類型被推斷為 string 類型
,而 getComponent 函數(shù)它的第二個參數(shù)有一個更具體的字面量類型
。這在實際場合中被拓寬了,所以導致了一個錯誤。
1.2.1 const 控制類型拓寬
如果用 const 而不是 let 聲明一個變量,那么它的類型會更窄。事實上,使用 const 可以幫助我們修復案例一例子中的錯誤:
const x = "x"; // type is "x"
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK
因為 x 不能重新賦值,所以 TypeScript 可以推斷更窄的類型,就不會在后續(xù)賦值中出現(xiàn)錯誤。
然而,const 對于對象和數(shù)組,仍然會存在問題
。
- 案例二
以下這段代碼在 JavaScript 中是沒有問題的:
const obj = {
x: 1,
};
obj.x = 6;
obj.x = '6';
obj.y = 8;
obj.name = 'semlinker';
但是在TypeScrip的環(huán)境中最后三局會出現(xiàn)錯誤:
const obj = {
x: 1,
};
obj.x = 6; // OK
// Type '"6"' is not assignable to type 'number'.
obj.x = '6'; // Error
// Property 'y' does not exist on type '{ x: number; }'.
obj.y = 8; // Error
// Property 'name' does not exist on type '{ x: number; }'.
obj.name = 'semlinker'; // Error
上述代碼中,對于 obj 的類型來說,它可以是 {readonly x:1} 類型,或者是更通用的 {x:number} 類型。當然也可能是 {[key: string]: number} 或 object 類型。TypeScript 的拓寬算法會將對象其內部屬性賦值給 let 關鍵字聲明的變量
,進而來推斷其屬性的類型。因此 obj 的類型為 {x:number} 。你可以將 obj.x 賦值給其他 number 類型的變量,但是它還會阻止你添加其他屬性。
1.2.2 提供顯式類型注釋
如果用給let聲明的變量設置顯示的類型注釋,也可以修復案例一例子中的錯誤:
let x: "x" = "x"; // type is "x"
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK
基于字面量類型拓寬的條件,我們可以通過添加顯示類型注解
控制類型拓寬行為。
const specifiedStr: 'this is string' = 'this is string'; // 類型是 '"this is string"'
let str2 = specifiedStr; // 即便使用 let 定義,類型是 'this is string'
1.2.3 使用 const 斷言
不要將const 斷言與 let 和 const 混淆,后者在值空間中引入符號。這是一個純粹的類型級構造。讓我們來看看以下變量的不同推斷類型:
// Type is { x: number; y: number; }
const obj1 = {
x: 1,
y: 2
};
// Type is { x: 1; y: number; }
const obj2 = {
x: 1 as const,
y: 2,
};
// Type is { readonly x: 1; readonly y: 2; }
const obj3 = {
x: 1,
y: 2
} as const;
當你在一個值之后使用 const 斷言時,TypeScript 將為它推斷出最窄的類型,沒有拓寬。當然你也可以對數(shù)組使用 const 斷言:
/ Type is number[]
const arr1 = [1, 2, 3];
// Type is readonly [1, 2, 3]
const arr2 = [1, 2, 3] as const;
二、類型縮小
在 TypeScript 中,我們可以通過某些操作將變量的類型由一個較為寬泛的集合縮小到相對較小、較明確的集合,這就是類型縮小。
比如,我們可以使用類型守衛(wèi)將函數(shù)參數(shù)的類型從 any 縮小到明確的類型:
let func = (anything: any) => {
if (typeof anything === 'string') {
return anything; // 類型是 string
} else if (typeof anything === 'number') {
return anything; // 類型是 number
}
return null;
};
同樣,我們可以使用類型守衛(wèi)將聯(lián)合類型縮小到明確的子類型,示例如下:
{
let func = (anything: string | number) => {
if (typeof anything === 'string') {
return anything; // 類型是 string
} else {
return anything; // 類型是 number
}
};
}
我們也可以通過字面量類型等值判斷(===)或其他控制流語句(包括但不限于 if、三目運算符、switch 分支)將聯(lián)合類型收斂為更具體的類型,如下代碼所示:
{
type Goods = 'pen' | 'pencil' |'ruler';
const getPenCost = (item: 'pen') => 2;
const getPencilCost = (item: 'pencil') => 4;
const getRulerCost = (item: 'ruler') => 6;
const getCost = (item: Goods) => {
if (item === 'pen') {
return getPenCost(item); // item => 'pen'
} else if (item === 'pencil') {
return getPencilCost(item); // item => 'pencil'
} else {
return getRulerCost(item); // item => 'ruler'
}
}
}
在上述 getCost 函數(shù)中,接受的參數(shù)類型是字面量類型的聯(lián)合類型,函數(shù)內包含了 if 語句的 3 個流程分支,其中每個流程分支調用的函數(shù)的參數(shù)都是具體獨立的字面量類型。
那為什么類型由多個字面量組成的變量 item 可以傳值給僅接收單一特定字面量類型的函數(shù) getPenCost、getPencilCost、getRulerCost 呢?這是因為在每個流程分支中,編譯器知道流程分支中的 item 類型是什么。比如 item === ‘pencil’ 的分支,item 的類型就被收縮為“pencil”。
事實上,如果我們將上面的示例去掉中間的流程分支,編譯器也可以推斷出收斂后的類型,如下代碼所示:
const getCost = (item: Goods) => {
if (item === 'pen') {
item; // item => 'pen'
} else {
item; // => 'pencil' | 'ruler'
}
}
一般來說 TypeScript 非常擅長通過條件來判別類型,但在處理一些特殊值時要特別注意 —— 它可能包含你不想要的東西!例如,以下從聯(lián)合類型中排除 null 的方法是錯誤的:
const el = document.getElementById("foo"); // Type is HTMLElement | null
if (typeof el === "object") {
el; // Type is HTMLElement | null
}
因為在 JavaScript 中 typeof null 的結果是 “object” ,所以你實際上并沒有通過這種檢查排除 null 值。除此之外,false的原始值也會產生類似的問題:
function foo(x?: number | string | null) {
if (!x) {
x; // Type is string | number | null | undefined
}
}
因為空字符串和 0 都屬于 false值,所以在分支中 x 的類型可能是 string 或 number 類型。幫助類型檢查器縮小類型的另一種常見方法是在它們上放置一個明確的 “標簽”:文章來源:http://www.zghlxwxcb.cn/news/detail-661408.html
interface UploadEvent {
type: "upload";
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download";
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download":
e; // Type is DownloadEvent
break;
case "upload":
e; // Type is UploadEvent
break;
}
}
這種模式也被稱為 ”標簽聯(lián)合“ 或 ”可辨識聯(lián)合“,它在 TypeScript 中的應用范圍非常廣。文章來源地址http://www.zghlxwxcb.cn/news/detail-661408.html
到了這里,關于Typescript基礎知識(類型拓寬、類型縮?。┑奈恼戮徒榻B完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!