TypeScript?類型收窄的具體使用
一、類型收窄概述
類型收窄是 TypeScript 類型系統(tǒng)中最為核心和強(qiáng)大的特性之一。它允許 TypeScript 在特定的代碼路徑中,根據(jù)條件判斷自動(dòng)縮小變量的類型范圍,從而提供更精確的類型信息和更安全的類型檢查。
在日常開(kāi)發(fā)中,我們經(jīng)常需要處理聯(lián)合類型、可選屬性、字面量類型等復(fù)雜類型場(chǎng)景。如果沒(méi)有類型收窄,我們不得不在每次使用時(shí)都進(jìn)行類型斷言,這不僅繁瑣,還容易引入運(yùn)行時(shí)錯(cuò)誤。類型收窄機(jī)制讓 TypeScript 能夠智能地推斷變量在特定上下文中的具體類型,大大提升了開(kāi)發(fā)效率和代碼質(zhì)量。
理解類型收窄不僅能幫助你寫出更簡(jiǎn)潔的代碼,還能讓你在面對(duì)復(fù)雜類型時(shí)游刃有余。本文將從基礎(chǔ)概念出發(fā),深入講解各種類型收窄技巧,并通過(guò)大量實(shí)例幫助你建立完整的類型收窄知識(shí)體系。
二、類型守衛(wèi)與類型收窄
2.1 什么是類型守衛(wèi)
類型守衛(wèi)(Type Guard)是一種特殊的表達(dá)式或函數(shù),它能夠告訴 TypeScript 編譯器某個(gè)變量在特定條件下具有更具體的類型。類型守衛(wèi)是實(shí)現(xiàn)類型收窄的主要手段。
TypeScript 內(nèi)置了多種類型守衛(wèi)機(jī)制,包括 typeof 操作符、instanceof 操作符、in 操作符等。同時(shí),我們也可以通過(guò)自定義類型守衛(wèi)函數(shù)來(lái)創(chuàng)建更復(fù)雜的類型判斷邏輯。
// typeof 類型守衛(wèi)
function processValue(value: string | number) {
if (typeof value === 'string') {
// 在這個(gè)分支中,value 被收窄為 string 類型
return value.toUpperCase();
} else {
// 在這個(gè)分支中,value 被收窄為 number 類型
return value * 2;
}
}
// instanceof 類型守衛(wèi)
class Dog {
bark() {
console.log('Woof!');
}
}
class Cat {
meow() {
console.log('Meow!');
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
2.2 自定義類型守衛(wèi)
當(dāng)內(nèi)置的類型守衛(wèi)無(wú)法滿足需求時(shí),我們可以創(chuàng)建自定義的類型守衛(wèi)函數(shù)。自定義類型守衛(wèi)的返回值類型使用 value is Type 的形式,告訴 TypeScript 返回 true 時(shí)變量的具體類型。
interface Fish {
swim(): void;
hasFins: boolean;
}
interface Bird {
fly(): void;
hasWings: boolean;
}
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
function handleAnimal(animal: Fish | Bird) {
if (isFish(animal)) {
// animal 被收窄為 Fish 類型
animal.swim();
console.log('Has fins:', animal.hasFins);
} else {
// animal 被收窄為 Bird 類型
animal.fly();
console.log('Has wings:', animal.hasWings);
}
}
自定義類型守衛(wèi)在處理復(fù)雜類型判斷時(shí)非常有用,可以將類型判斷邏輯封裝成可復(fù)用的函數(shù),使代碼更加清晰和易于維護(hù)。
2.3 類型守衛(wèi)的組合使用
在實(shí)際開(kāi)發(fā)中,我們經(jīng)常需要組合使用多個(gè)類型守衛(wèi)來(lái)處理復(fù)雜的類型判斷場(chǎng)景。TypeScript 能夠正確地追蹤多個(gè)類型守衛(wèi)的組合效果,實(shí)現(xiàn)多層類型收窄。
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rectangle'; width: number; height: number };
function calculateArea(shape: Shape): number {
if (shape.kind === 'circle') {
// 收窄為 circle
return Math.PI * shape.radius ** 2;
}
if (shape.kind === 'square') {
// 收窄為 square
return shape.side ** 2;
}
// 收窄為 rectangle
return shape.width * shape.height;
}
// 組合使用多個(gè)條件
function describeShape(shape: Shape): string {
if (shape.kind === 'circle' && shape.radius > 10) {
return 'Large circle';
}
if (shape.kind === 'square' || shape.kind === 'rectangle') {
if ('side' in shape) {
return 'Square with side ' + shape.side;
}
return `Rectangle ${shape.width}x${shape.height}`;
}
return 'Unknown shape';
}
三、可辨識(shí)聯(lián)合與類型收窄
3.1 可辨識(shí)聯(lián)合模式
可辨識(shí)聯(lián)合(Discriminated Union)是 TypeScript 中處理相關(guān)類型的一種強(qiáng)大模式。它通過(guò)一個(gè)共有的字面量屬性(可辨識(shí)屬性)來(lái)區(qū)分聯(lián)合中的不同成員,TypeScript 可以根據(jù)這個(gè)屬性的值進(jìn)行精確的類型收窄。
// 定義可辨識(shí)聯(lián)合類型
type PaymentStatus =
| { status: 'pending' }
| { status: 'processing' }
| { status: 'success'; transactionId: string }
| { status: 'failed'; errorCode: number; message: string };
// 處理支付狀態(tài)
function handlePayment(status: PaymentStatus) {
switch (status.status) {
case 'pending':
console.log('Payment is pending...');
break;
case 'processing':
console.log('Payment is being processed...');
break;
case 'success':
// status 被收窄為 { status: 'success'; transactionId: string }
console.log('Payment successful! Transaction ID:', status.transactionId);
break;
case 'failed':
// status 被收窄為 { status: 'failed'; errorCode: number; message: string }
console.log('Payment failed:', status.message, 'Code:', status.errorCode);
break;
}
}
可辨識(shí)聯(lián)合模式的核心在于可辨識(shí)屬性的選擇。這個(gè)屬性應(yīng)該是每個(gè)類型成員都擁有的字面量類型,且不同成員的值應(yīng)該互不相同。這樣 TypeScript 才能準(zhǔn)確地區(qū)分和收窄類型。
3.2 窮盡性檢查
可辨識(shí)聯(lián)合的一個(gè)強(qiáng)大特性是支持窮盡性檢查(Exhaustiveness Checking)。當(dāng)我們?cè)?switch 語(yǔ)句或 if-else 鏈中處理所有可能的類型成員時(shí),TypeScript 能夠確保我們不會(huì)遺漏任何情況。
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
function handleRequest(method: HTTPMethod, url: string, body?: unknown) {
switch (method) {
case 'GET':
return fetch(url, { method: 'GET' });
case 'POST':
return fetch(url, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined
});
case 'PUT':
return fetch(url, {
method: 'PUT',
body: body ? JSON.stringify(body) : undefined
});
case 'DELETE':
return fetch(url, { method: 'DELETE' });
default:
// 窮盡性檢查:如果遺漏了任何情況,這里會(huì)報(bào)錯(cuò)
const _exhaustive: never = method;
throw new Error(`Unknown HTTP method: ${_exhaustive}`);
}
}
當(dāng)添加新的 HTTPMethod 類型時(shí),default 分支會(huì)產(chǎn)生類型錯(cuò)誤,提示開(kāi)發(fā)者需要處理新的情況。這種機(jī)制能夠有效防止遺漏處理新添加的類型成員。
3.3 嵌套可辨識(shí)聯(lián)合
復(fù)雜的業(yè)務(wù)場(chǎng)景往往需要使用嵌套的可辨識(shí)聯(lián)合來(lái)處理多層次的數(shù)據(jù)結(jié)構(gòu)。TypeScript 能夠正確地追蹤多層嵌套的類型收窄。
type APIResponse<T> =
| { success: true; data: T }
| { success: false; error: { code: number; message: string } };
type User = {
id: number;
name: string;
email: string;
};
type Order = {
orderId: string;
items: Array<{ productId: string; quantity: number }>;
total: number;
};
type UserResponse = APIResponse<User>;
type OrderResponse = APIResponse<Order>;
function handleResponse<T>(
response: APIResponse<T>,
onSuccess: (data: T) => void,
onError: (error: { code: number; message: string }) => void
) {
if (response.success) {
// response 被收窄為 { success: true; data: T }
onSuccess(response.data);
} else {
// response 被收窄為 { success: false; error: { code: number; message: string } }
onError(response.error);
}
}
// 使用示例
const userResponse: UserResponse = {
success: true,
data: { id: 1, name: 'John', email: 'john@example.com' }
};
handleResponse(
userResponse,
(user) => console.log('User:', user.name),
(error) => console.error('Error:', error.message)
);
四、類型謂詞與類型斷言
4.1 類型謂詞詳解
類型謂詞(Type Predicate)是自定義類型守衛(wèi)的核心語(yǔ)法,形式為 parameterName is Type。它允許我們精確地控制 TypeScript 如何收窄參數(shù)的類型。
interface Admin {
role: 'admin';
permissions: string[];
}
interface User {
role: 'user';
username: string;
}
interface Guest {
role: 'guest';
visitCount: number;
}
type AuthenticatedUser = Admin | User | Guest;
// 類型謂詞函數(shù)
function isAdmin(user: AuthenticatedUser): user is Admin {
return user.role === 'admin';
}
function isUser(user: AuthenticatedUser): user is User {
return user.role === 'user';
}
function isGuest(user: AuthenticatedUser): user is Guest {
return user.role === 'guest';
}
// 使用類型謂詞
function getUserPermissions(user: AuthenticatedUser): string[] {
if (isAdmin(user)) {
// user 被收窄為 Admin
return user.permissions;
}
if (isUser(user)) {
// user 被收窄為 User
return []; // 普通用戶沒(méi)有特殊權(quán)限
}
// user 被收窄為 Guest
return [];
}
類型謂詞的強(qiáng)大之處在于它可以表達(dá)復(fù)雜的類型判斷邏輯,而不僅僅局限于簡(jiǎn)單的屬性檢查。這使得我們可以創(chuàng)建高度定制化的類型守衛(wèi)。
4.2 類型斷言的使用場(chǎng)景
類型斷言(Type Assertion)允許我們顯式地告訴 TypeScript 某個(gè)值的類型。與類型收窄不同,類型斷言是開(kāi)發(fā)者主動(dòng)告訴編譯器「我知道這個(gè)類型是什么」,而不是讓編譯器通過(guò)代碼邏輯推斷。
// 使用 as 語(yǔ)法進(jìn)行類型斷言
const myCanvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const ctx = myCanvas.getContext('2d'); // 現(xiàn)在可以訪問(wèn) Canvas API
// 使用尖括號(hào)語(yǔ)法(僅在 .tsx 文件外可用)
const myInput = document.getElementById('myInput') as HTMLInputElement;
const value = myInput.value;
// 非空斷言
const elements = document.querySelectorAll('.item')!;
elements.forEach(el => el.textContent);
// 類型斷言 vs 類型守衛(wèi)
interface APIResponse {
data: unknown;
}
function handleResponse(response: APIResponse) {
// 類型斷言:開(kāi)發(fā)者確信 data 是 User 類型
const user = response.data as User;
console.log(user.name);
// 類型守衛(wèi):通過(guò)代碼邏輯驗(yàn)證類型
if (isUser(response.data)) {
console.log(response.data.name); // TypeScript 已知這是 User
}
}
需要注意的是,類型斷言是信任開(kāi)發(fā)者的,如果斷言錯(cuò)誤,可能導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。因此,應(yīng)該優(yōu)先使用類型守衛(wèi)進(jìn)行類型收窄,只有在確實(shí)知道類型的情況下才使用類型斷言。
4.3 雙重?cái)嘌耘c類型斷言的風(fēng)險(xiǎn)
在某些情況下,我們需要使用雙重?cái)嘌詠?lái)繞過(guò) TypeScript 的類型檢查。但這種做法存在風(fēng)險(xiǎn),應(yīng)該謹(jǐn)慎使用。
// 雙重?cái)嘌裕簭?A 類型斷言到 unknown,再斷言到目標(biāo)類型
const value = someValue as unknown as TargetType;
// 示例:處理 DOM 元素
const element = document.getElementById('my-element');
// 正常情況
const input = element as HTMLInputElement;
// 雙重?cái)嘌裕寒?dāng)你確定元素是特定類型,但 TypeScript 無(wú)法推斷時(shí)
const widget = element as unknown as CustomWidget;
// 使用雙重?cái)嘌缘娘L(fēng)險(xiǎn)
// 如果元素實(shí)際上不是 CustomWidget 類型,運(yùn)行時(shí)可能出錯(cuò)
雙重?cái)嘌詰?yīng)該作為最后手段使用。在使用之前,應(yīng)該考慮是否有更好的類型設(shè)計(jì)方式,或者是否可以通過(guò)類型守衛(wèi)來(lái)實(shí)現(xiàn)類型收窄。
五、索引訪問(wèn)類型與映射類型
5.1 索引訪問(wèn)類型
索引訪問(wèn)類型(Indexed Access Types)允許我們通過(guò)索引獲取類型的成員,從而創(chuàng)建更靈活的類型定義。
interface Person {
name: string;
age: number;
address: {
city: string;
zip: string;
};
}
// 獲取單個(gè)屬性類型
type PersonName = Person['name']; // string
type PersonAge = Person['age']; // number
// 獲取嵌套屬性類型
type PersonCity = Person['address']['city']; // string
// 使用聯(lián)合類型作為索引
type PersonAddressKeys = Person['address'][keyof Person['address']]; // string
// 獲取數(shù)組元素類型
type StringArray = string[];
type StringArrayElement = StringArray[number]; // string
// 在函數(shù)中使用索引訪問(wèn)類型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = {
name: 'Alice',
age: 30,
address: { city: 'NYC', zip: '10001' }
};
const name = getProperty(person, 'name'); // string
const city = getProperty(person, 'address'); // { city: string; zip: string }
5.2 映射類型
映射類型(Mapped Types)允許我們基于現(xiàn)有類型創(chuàng)建新類型,對(duì)類型的每個(gè)屬性進(jìn)行轉(zhuǎn)換。
interface Original {
id: number;
name: string;
age: number;
email: string;
}
// 將所有屬性變?yōu)榭蛇x
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialPerson = Partial<Person>;
// { id?: number; name?: string; age?: number; email?: string }
// 將所有屬性變?yōu)橹蛔x
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyPerson = Readonly<Person>;
// { readonly id: number; readonly name: string; ... }
// 將所有屬性變?yōu)榭蛇x且只讀
type OptionalReadonly<T> = {
readonly [P in keyof T]?: T[P];
};
// 映射類型的內(nèi)置實(shí)現(xiàn)
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type PersonBasic = Pick<Person, 'id' | 'name'>;
// { id: number; name: string }
type Omit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
type PersonWithoutEmail = Omit<Person, 'email'>;
// { id: number; name: string; age: number }
5.3 條件類型與分布式特性
條件類型(Conditional Types)是 TypeScript 類型系統(tǒng)中最靈活的特性之一,它允許我們根據(jù)條件選擇不同的類型。
// 條件類型基本語(yǔ)法
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// 分布式條件類型
// 當(dāng) T 是聯(lián)合類型時(shí),條件類型會(huì)分別應(yīng)用于每個(gè)成員
type ToArray<T> = T[];
type Test3 = ToArray<string | number>; // string[] | number[]
// 正確實(shí)現(xiàn):分布式
type ToArrayDistributed<T> = T extends any ? T[] : never;
type Test4 = ToArrayDistributed<string | number>; // string[] | number[]
// 實(shí)際應(yīng)用:根據(jù)類型選擇不同的處理方式
type Response<T> = T extends string
? { type: 'text'; content: T }
: { type: 'binary'; data: T };
type TextResponse = Response<string>;
// { type: 'text'; content: string }
type BinaryResponse = Response<Blob>;
// { type: 'binary'; data: Blob }
六、模板字面量類型與類型收窄
6.1 模板字面量類型基礎(chǔ)
TypeScript 4.1 引入了模板字面量類型(Template Literal Types),允許我們?cè)陬愋图?jí)別使用模板字面量語(yǔ)法。這為類型系統(tǒng)帶來(lái)了很大的字符串處理能力。
// 基本模板字面量類型
type Greeting = `Hello, ${string}`;
// 匹配任何以 "Hello, " 開(kāi)頭的字符串
type Email = `${string}@${string}.${string}`;
// 匹配基本的 email 格式
// 使用聯(lián)合類型
type EventName = `on${'Click' | 'Hover' | 'Focus'}`;
// "onClick" | "onHover" | "onFocus"
// 模板字面量類型與收窄
type CSSProperty =
| `${'margin' | 'padding'}-${'top' | 'right' | 'bottom' | 'left'}`;
// "margin-top" | "margin-right" | ... | "padding-left"
function setCSSProperty(prop: CSSProperty, value: string) {
// 在函數(shù)內(nèi)部,prop 被收窄為具體的 CSS 屬性名
document.documentElement.style.setProperty(`--${prop}`, value);
}
setCSSProperty('margin-top', '10px'); // OK
setCSSProperty('padding-left', '20px'); // OK
// setCSSProperty('border-radius', '5px'); // Error
6.2 模板字面量類型的高級(jí)用法
模板字面量類型可以與條件類型、映射類型等組合使用,創(chuàng)建復(fù)雜的類型轉(zhuǎn)換邏輯。
// 使用 infer 進(jìn)行類型推斷
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Func1 = () => number;
type Func2 = (x: string) => boolean;
type Func3 = (a: number, b: string) => { name: string };
type R1 = GetReturnType<Func1>; // number
type R2 = GetReturnType<Func2>; // boolean
type R3 = GetReturnType<Func3>; // { name: string }
// 模板字面量類型中的 infer
type ExtractEventParams<T> = T extends `on${infer Event}` ? Event : never;
type Events = 'onClick' | 'onHover' | 'onSubmit' | 'customEvent';
type EventNames = ExtractEventParams<Events>;
// "Click" | "Hover" | "Submit" | "customEvent"
// 組合使用:創(chuàng)建完整的 API 路徑類型
type HTTPMethod = 'get' | 'post' | 'put' | 'delete';
type BasePath = '/api' | '/v2';
type Resource = 'users' | 'posts' | 'comments';
type APIEndpoint = `${Uppercase<HTTPMethod>} ${BasePath}/${Resource}`;
// "GET /api/users" | "GET /api/posts" | ... | "DELETE /v2/comments"
6.3 字符串類型收窄的實(shí)際應(yīng)用
模板字面量類型在處理字符串字面量類型時(shí)特別有用,可以實(shí)現(xiàn)精確的字符串類型收窄。
// 狀態(tài)機(jī)類型
type State =
| { kind: 'idle' }
| { kind: 'loading' }
| { kind: 'success'; data: unknown }
| { kind: 'error'; message: string };
// 使用模板字面量類型創(chuàng)建 Action 類型
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: unknown }
| { type: 'FETCH_ERROR'; error: string }
| { type: 'RESET' };
// Action 處理函數(shù)
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'FETCH_START':
return { kind: 'loading' };
case 'FETCH_SUCCESS':
return { kind: 'success', data: action.payload };
case 'FETCH_ERROR':
return { kind: 'error', message: action.error };
case 'RESET':
return { kind: 'idle' };
default:
// 窮盡性檢查
const _: never = action;
return state;
}
}
七、類型收窄在泛型中的應(yīng)用
7.1 泛型約束與類型收窄
泛型與類型收窄的結(jié)合是 TypeScript 類型系統(tǒng)的高級(jí)應(yīng)用。通過(guò)泛型約束和類型守衛(wèi),我們可以創(chuàng)建靈活且類型安全的通用函數(shù)。
interface HasId {
id: number;
}
interface HasName {
name: string;
}
type WithId<T> = T extends HasId ? T : never;
type WithName<T> = T extends HasName ? T : never;
// 條件類型實(shí)現(xiàn)類型收窄
function processItem<T>(item: T): T extends HasId ? T : never {
if ('id' in item) {
return item as T extends HasId ? T : never;
}
throw new Error('Item must have id');
}
// 使用泛型約束
function getId<T extends HasId>(item: T): number {
return item.id;
}
function getName<T extends HasName>(item: T): string {
return item.name;
}
// 泛型函數(shù)中的類型收窄
function firstItem<T>(arr: T[]): T | undefined {
return arr[0];
}
function processArray<T extends HasId>(arr: T[]): number[] {
const first = firstItem(arr);
if (first) {
// first 被收窄為 T & HasId
return [first.id];
}
return [];
}
7.2 多泛型參數(shù)的類型收窄
當(dāng)函數(shù)有多個(gè)泛型參數(shù)時(shí),類型收窄變得更加復(fù)雜。我們需要考慮不同參數(shù)之間的類型關(guān)系。
// 關(guān)聯(lián)類型的類型收窄
function pair<T, U extends T>(first: T, second: U): [T, U] {
return [first, second];
}
const p1 = pair('hello', 'world'); // [string, string]
const p2 = pair(42, 100); // [number, number]
// const p3 = pair('hello', 42); // Error: second must extend first
// 使用類型守衛(wèi)進(jìn)行多泛型收窄
function isSameType<T, U>(a: T, b: U): a is T & U {
return typeof a === typeof b;
}
function merge<T, U>(a: T, b: U): T & U {
if (isSameType(a, b)) {
// a 和 b 被收窄為相同類型
return { ...a, ...b } as T & U;
}
return { ...a, ...b } as T & U;
}
7.3 泛型工廠函數(shù)中的類型收窄
泛型工廠函數(shù)是創(chuàng)建類型安全對(duì)象的強(qiáng)大工具,結(jié)合類型收窄可以實(shí)現(xiàn)復(fù)雜的類型轉(zhuǎn)換邏輯。
// 工廠函數(shù)模式
interface Factory<T> {
create(): T;
}
class StringFactory implements Factory<string> {
create() {
return 'default string';
}
}
class NumberFactory implements Factory<number> {
create() {
return 42;
}
}
function createFactory<T>(factory: Factory<T>): T {
return factory.create();
}
// 使用泛型工廠
const str = createFactory(new StringFactory()); // string
const num = createFactory(new NumberFactory()); // number
// 泛型與條件類型
type FactoryReturn<T> = T extends Factory<infer R> ? R : never;
type StringReturn = FactoryReturn<StringFactory>; // string
type NumberReturn = FactoryReturn<NumberFactory>; // number
八、類型收窄的最佳實(shí)踐
8.1 何時(shí)使用類型收窄
類型收窄應(yīng)該成為 TypeScript 開(kāi)發(fā)的日常實(shí)踐。以下是一些適合使用類型收窄的場(chǎng)景。
處理聯(lián)合類型時(shí),類型收窄是最自然的選擇。無(wú)論是可選屬性、可空值還是多態(tài)類型,類型收窄都能提供精確的類型信息。
// 處理可選屬性
interface User {
name: string;
email?: string;
}
function sendEmail(user: User) {
if (user.email) {
// user.email 被收窄為 string(不再是 string | undefined)
sendToAddress(user.email);
}
}
// 處理 null | undefined
function processValue(value: string | null | undefined) {
if (value != null) {
// value 被收窄為 string
console.log(value.toUpperCase());
}
}
8.2 避免過(guò)度類型斷言
雖然類型斷言是 TypeScript 的重要特性,但應(yīng)該優(yōu)先使用類型收窄。類型斷言相當(dāng)于告訴編譯器「我知道我在做什么」,這意味著放棄了 TypeScript 的類型檢查。
// 不推薦:過(guò)度使用類型斷言
function badExample(value: unknown) {
const str = value as string; // 危險(xiǎn)!如果 value 不是 string,運(yùn)行時(shí)可能出錯(cuò)
return str.toUpperCase();
}
// 推薦:使用類型守衛(wèi)
function goodExample(value: unknown) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return 'Not a string';
}
// 如果必須使用類型斷言,使用類型斷言函數(shù)封裝
function assertString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Expected string');
}
}
function anotherExample(value: unknown) {
assertString(value);
// 現(xiàn)在 value 被收窄為 string
return value.toUpperCase();
}
8.3 利用類型系統(tǒng)發(fā)現(xiàn)潛在問(wèn)題
類型收窄不僅是一種編碼技巧,更是一種發(fā)現(xiàn)潛在問(wèn)題的工具。當(dāng) TypeScript 無(wú)法正確收窄類型時(shí),往往意味著代碼中存在設(shè)計(jì)問(wèn)題或邏輯漏洞。
// TypeScript 無(wú)法收窄時(shí),可能需要重新設(shè)計(jì)類型
type Response<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResponse<T>(response: Response<T>) {
if (response.success) {
return response.data;
}
// 在這里,response 被收窄為 { success: false; error: string }
// 如果忘記處理 error 情況,TypeScript 不會(huì)報(bào)錯(cuò)
// 但我們可以添加默認(rèn)處理
throw new Error(response.error || 'Unknown error');
}
九、總結(jié)
類型收窄是 TypeScript 類型系統(tǒng)的核心特性,它讓我們能夠在代碼中實(shí)現(xiàn)精確的類型推斷和安全的類型檢查。從簡(jiǎn)單的 typeof 檢查到復(fù)雜的可辨識(shí)聯(lián)合,從類型謂詞到模板字面量類型,TypeScript 提供了豐富的工具來(lái)處理各種類型場(chǎng)景。
掌握類型收窄不僅能寫出更安全的代碼,還能提升開(kāi)發(fā)效率。當(dāng)你熟悉了類型收窄的技巧后,你會(huì)發(fā)現(xiàn)處理復(fù)雜類型變得自然而直觀。同時(shí),類型收窄也是理解 TypeScript 高級(jí)類型系統(tǒng)的基礎(chǔ),對(duì)于深入學(xué)習(xí) TypeScript 至關(guān)重要。
在實(shí)際開(kāi)發(fā)中,應(yīng)該養(yǎng)成使用類型收窄的習(xí)慣,優(yōu)先選擇類型守衛(wèi)而非類型斷言,讓 TypeScript 的類型系統(tǒng)為你的代碼保駕護(hù)航。
到此這篇關(guān)于TypeScript 類型收窄的具體使用的文章就介紹到這了,更多相關(guān)TypeScript 類型收窄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript分析、壓縮工具JavaScript Analyser
這篇文章主要介紹了JavaScript分析、壓縮工具JavaScript Analyser,需要的朋友可以參考下2014-12-12
比較簡(jiǎn)單實(shí)用的使用正則三種版本的js去空格處理方法
比較簡(jiǎn)單實(shí)用的使用正則三種版本的js去空格處理方法...2007-11-11
JavaScript實(shí)現(xiàn)一鍵復(fù)制文本功能的示例代碼
這篇文章主要為大家介紹兩種javascript實(shí)現(xiàn)文本復(fù)制(將文本寫入剪貼板)的方法,文中的示例代碼講解詳細(xì),大家可以根據(jù)需求特點(diǎn)選用2023-03-03
js立即執(zhí)行函數(shù): (function ( ){})( ) 與 (function ( ){}( )) 有什么區(qū)別?
這篇文章主要介紹了js立即執(zhí)行函數(shù): (function ( ){})( ) 與 (function ( ){}( )) 有什么區(qū)別,需要的朋友可以參考下2015-11-11
JS實(shí)現(xiàn)上傳圖片的三種方法并實(shí)現(xiàn)預(yù)覽圖片功能
在用戶注冊(cè)頁(yè)面,需要用戶在本地選擇一張圖片作為頭像,并同時(shí)預(yù)覽,實(shí)現(xiàn)思路有兩種,具體實(shí)現(xiàn)方法和實(shí)例代碼大家參考下本文2017-07-07
JavaScript中的Window.open()用法示例詳解
這篇文章主要給大家介紹了關(guān)于JavaScript中Window.open()用法的相關(guān)資料,今天在項(xiàng)目中用到了彈出子窗口,就想到了用JavaScript實(shí)現(xiàn)的兩種方法,其中一個(gè)就是window.open(),需要的朋友可以參考下2023-07-07

