12個(gè)編寫整潔TypeScript代碼的技巧
本文將探討十二個(gè)用于編寫整潔 TypeScript 代碼的技巧,并通過(guò)示例展示它們的工作原理以及為何有用。在你自己的 TypeScript 代碼中使用這些技巧,可以創(chuàng)建更健壯、更易于維護(hù)的應(yīng)用程序,使其更易于理解和調(diào)試。
1. 使用類型注解
TypeScript 是一種靜態(tài)類型語(yǔ)言,這意味著你可以為變量和函數(shù)定義類型。使用類型注解有助于在開(kāi)發(fā)過(guò)程早期捕獲錯(cuò)誤,并提高代碼的可讀性。
以下是 TypeScript 中類型注解的一些示例:
// 顯式指定變量的數(shù)據(jù)類型
let count: number = 0;
// 顯式指定函數(shù)參數(shù)和返回值的數(shù)據(jù)類型
function addNumbers(a: number, b: number): number {
return a + b;
}
// 顯式指定類屬性的數(shù)據(jù)類型
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
getDetails(): string {
return `${this.name} is ${this.age} years old.`;
}
}
在這些示例中,我們使用類型注解來(lái)指定變量、函數(shù)參數(shù)、函數(shù)返回值和類屬性的數(shù)據(jù)類型。類型注解寫在變量、參數(shù)或?qū)傩悦螅妹疤?hào)(:)分隔,后面跟著所需的數(shù)據(jù)類型。
2. 使用枚舉
枚舉是 TypeScript 的一個(gè)強(qiáng)大功能,允許你定義一組命名常量。它們可以使你的代碼更具可讀性和可維護(hù)性,并減少因魔術(shù)數(shù)字(magic numbers)導(dǎo)致錯(cuò)誤的可能性。
以下是在 TypeScript 中如何使用枚舉的示例:
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
function printColor(color: Color): void {
console.log(`The color is ${color}`);
}
printColor(Color.Red); // 輸出:The color is RED
在這個(gè)示例中,我們定義了一個(gè)名為Color的枚舉,它包含三個(gè)命名常量:Red、Green和Blue。每個(gè)常量都有一個(gè)關(guān)聯(lián)的值,可以是字符串或數(shù)字。然后我們定義了一個(gè)名為printColor的函數(shù),它接受一個(gè)Color參數(shù),并使用該參數(shù)值在控制臺(tái)中記錄一條消息。
當(dāng)我們使用Color.Red常量作為參數(shù)調(diào)用printColor函數(shù)時(shí),它會(huì)在控制臺(tái)中記錄消息 "The color is RED"。
3. 使用可選鏈
可選鏈?zhǔn)?TypeScript 的一個(gè)特性,允許你安全地訪問(wèn)嵌套屬性和方法,而無(wú)需擔(dān)心中間值是否為null或undefined。這有助于減少運(yùn)行時(shí)錯(cuò)誤的可能性,并使你的代碼更健壯。
以下是在 TypeScript 中如何使用可選鏈的示例:
interface Person {
name: string;
address?: {
street: string;
city: string;
state: string;
};
}
const person1: Person = {
name: "John",
address: {
street: "123 Main St",
city: "Anytown",
state: "CA",
},
};
const person2: Person = {
name: "Jane",
};
console.log(person1?.address?.city); // 輸出:Anytown
console.log(person2?.address?.city); // 輸出:undefined
在這個(gè)示例中,我們有一個(gè)名為Person的接口,它定義了一個(gè)可選的address屬性,該屬性是一個(gè)具有street、city和state屬性的對(duì)象。然后我們創(chuàng)建了兩個(gè)Person類型的對(duì)象,一個(gè)帶有address屬性,一個(gè)沒(méi)有。
我們使用可選鏈安全地訪問(wèn)address對(duì)象的city屬性,即使address屬性或其任何子屬性為undefined或null。如果鏈中的任何屬性為undefined或null,表達(dá)式將返回undefined而不是拋出TypeError。
4. 使用空值合并運(yùn)算符
空值合并運(yùn)算符是 TypeScript 的另一個(gè)特性,可以使你的代碼更健壯。它允許你在變量或表達(dá)式為null或undefined時(shí)提供默認(rèn)值,而不依賴于假值(falsy values)。
以下是在 TypeScript 中如何使用空值合并運(yùn)算符的示例:
let value1: string | null = null;
let value2: string | undefined = undefined;
let value3: string | null | undefined = "hello";
console.log(value1?? "default value"); // 輸出:"default value"
console.log(value2?? "default value"); // 輸出:"default value"
console.log(value3?? "default value"); // 輸出:"hello"
在這個(gè)示例中,我們有三個(gè)可能包含null或undefined值的變量。我們使用空值合并運(yùn)算符(??)來(lái)檢查值是否為null或undefined,并在這種情況下提供默認(rèn)值。
在前兩種情況下,變量value1和value2分別為null和undefined,因此返回默認(rèn)值。在第三種情況下,變量value3包含一個(gè)非null/非undefined值,因此返回該值而不是默認(rèn)值。
5. 使用泛型
泛型是 TypeScript 的一個(gè)強(qiáng)大功能,允許你編寫可重用的代碼,該代碼可以與不同類型一起工作。它們可以幫助減少代碼重復(fù)并提高代碼的可維護(hù)性。
以下是在 TypeScript 中如何使用泛型的示例:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello"); // 輸出:"hello"
let output2 = identity<number>(42); // 輸出:42
在這個(gè)示例中,我們定義了一個(gè)名為identity的函數(shù),它接受一個(gè)類型參數(shù)T并返回與傳入值相同類型的值。該函數(shù)可以處理任何類型的數(shù)據(jù),實(shí)際的數(shù)據(jù)類型在函數(shù)調(diào)用時(shí)指定。
然后我們用兩種不同的數(shù)據(jù)類型調(diào)用identity函數(shù):一個(gè)字符串和一個(gè)數(shù)字。函數(shù)返回與傳入值相同類型的值,因此output1是字符串類型,output2是數(shù)字類型。
6. 使用接口
接口是 TypeScript 的另一個(gè)強(qiáng)大功能,可以幫助你編寫整潔且可讀的代碼。它們?cè)试S你為類、對(duì)象或函數(shù)定義契約,這可以幫助你避免常見(jiàn)錯(cuò)誤并使你的代碼更具自文檔性。
以下是在 TypeScript 中如何使用接口的示例:
interface Person {
firstName: string;
lastName: string;
age?: number;
}
function sayHello(person: Person): void {
console.log(`Hello, ${person.firstName} ${person.lastName}!`);
if (person.age) {
console.log(`You are ${person.age} years old.`);
}
}
let person1 = { firstName: "John", lastName: "Doe", age: 30 };
let person2 = { firstName: "Jane", lastName: "Doe" };
sayHello(person1); // 輸出:"Hello, John Doe! You are 30 years old."
sayHello(person2); // 輸出:"Hello, Jane Doe!"
在這個(gè)示例中,我們定義了一個(gè)名為Person的接口,它指定了person對(duì)象的形狀,包括firstName和lastName屬性以及一個(gè)可選的age屬性。然后我們定義了一個(gè)名為sayHello的函數(shù),它接受一個(gè)Person對(duì)象作為參數(shù),并在控制臺(tái)中打印問(wèn)候語(yǔ)。
我們創(chuàng)建了兩個(gè)與Person接口形狀匹配的對(duì)象,并將它們傳遞給sayHello函數(shù)。該函數(shù)能夠訪問(wèn)每個(gè)對(duì)象的firstName和lastName屬性,并在將age屬性打印到控制臺(tái)之前檢查它是否存在。
7. 使用解構(gòu)
解構(gòu)是一種簡(jiǎn)寫語(yǔ)法,允許你從數(shù)組和對(duì)象中提取值。它可以使你的代碼更具可讀性和簡(jiǎn)潔性,并減少因變量名不匹配導(dǎo)致錯(cuò)誤的可能性。
以下是在 TypeScript 中如何使用解構(gòu)的一些示例:
對(duì)象解構(gòu):
let person = { firstName: "John", lastName: "Doe", age: 30 };
let { firstName, lastName } = person;
console.log(firstName); // 輸出:"John"
console.log(lastName); // 輸出:"Doe"
在這個(gè)示例中,我們創(chuàng)建了一個(gè)名為person的對(duì)象,具有三個(gè)屬性。然后我們使用對(duì)象解構(gòu)來(lái)提取firstName和lastName屬性,并將它們分配給同名變量。這使我們能夠更輕松地訪問(wèn)這些屬性,并且使用更少的代碼。
數(shù)組解構(gòu):
let numbers = [1, 2, 3, 4, 5];
let [first, second,, fourth] = numbers;
console.log(first); // 輸出:1
console.log(second); // 輸出:2
console.log(fourth); // 輸出:4
在這個(gè)示例中,我們創(chuàng)建了一個(gè)數(shù)字?jǐn)?shù)組,并使用數(shù)組解構(gòu)來(lái)提取第一、第二和第四個(gè)元素,并將它們分配給變量。我們使用解構(gòu)模式中的空槽跳過(guò)第三個(gè)元素。這使我們能夠更輕松地訪問(wèn)數(shù)組中的特定元素,并且使用更少的代碼。
解構(gòu)也可以與函數(shù)參數(shù)一起使用,允許你從作為參數(shù)傳遞的對(duì)象中提取特定值:
function greet({ firstName, lastName }: { firstName: string, lastName: string }): void {
console.log(`Hello, ${firstName} ${lastName}!`);
}
let person = { firstName: "John", lastName: "Doe", age: 30 };
greet(person); // 輸出:"Hello, John Doe!"
在這個(gè)示例中,我們定義了一個(gè)名為greet的函數(shù),它使用解構(gòu)語(yǔ)法在函數(shù)參數(shù)中接受一個(gè)具有firstName和lastName屬性的對(duì)象。然后我們傳入一個(gè)person對(duì)象,greet函數(shù)能夠提取firstName和lastName屬性并在控制臺(tái)日志語(yǔ)句中使用它們。
8. 使用異步/等待
異步/等待是 TypeScript 的一個(gè)強(qiáng)大功能,允許你編寫看起來(lái)和行為類似于同步代碼的異步代碼。它可以提高代碼的可讀性并減少因回調(diào)地獄(callback hell)導(dǎo)致錯(cuò)誤的可能性。
以下是在 TypeScript 中如何使用異步/等待的示例:
async function getData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
getData().then((data) => {
console.log(data);
}).catch((error) => {
console.error(error);
});
在這個(gè)示例中,我們定義了一個(gè)名為getData的異步函數(shù),它向一個(gè) API 發(fā)出fetch請(qǐng)求,并使用await關(guān)鍵字等待響應(yīng)。然后我們使用json()方法解析響應(yīng),并再次使用await等待結(jié)果。最后,我們返回?cái)?shù)據(jù)對(duì)象。
然后我們調(diào)用getData()函數(shù),并使用then()方法處理返回的數(shù)據(jù),或者使用catch()方法處理可能發(fā)生的任何錯(cuò)誤。
9. 使用函數(shù)式編程技術(shù)
函數(shù)式編程技術(shù),如不可變性、純函數(shù)和高階函數(shù),可以幫助你編寫整潔且可維護(hù)的代碼。它們可以幫助減少副作用并使你的代碼更具可預(yù)測(cè)性和可測(cè)試性。
純函數(shù):純函數(shù)是沒(méi)有副作用且對(duì)于相同輸入始終返回相同輸出的函數(shù)。純函數(shù)使代碼更易于理解,并有助于防止錯(cuò)誤。以下是一個(gè)純函數(shù)的示例:
function add(a: number, b: number): number {
return a + b;
}
高階函數(shù):高階函數(shù)是接受一個(gè)或多個(gè)函數(shù)作為參數(shù)或返回一個(gè)函數(shù)作為結(jié)果的函數(shù)。高階函數(shù)可用于創(chuàng)建可重用代碼并簡(jiǎn)化復(fù)雜邏輯。以下是一個(gè)高階函數(shù)的示例:
function map<T, U>(arr: T[], fn: (arg: T) => U): U[] {
const result = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
}
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = map(numbers, (n) => n * 2);
console.log(doubledNumbers); // 輸出:[2, 4, 6, 8, 10]
在這個(gè)示例中,map函數(shù)接受一個(gè)數(shù)組和一個(gè)函數(shù)作為參數(shù),并將該函數(shù)應(yīng)用于數(shù)組中的每個(gè)元素,返回一個(gè)包含結(jié)果的新數(shù)組。
不可變數(shù)據(jù):不可變數(shù)據(jù)是創(chuàng)建后不能更改的數(shù)據(jù)。在函數(shù)式編程中,強(qiáng)調(diào)不可變性以防止副作用并使代碼更易于理解。以下是使用不可變數(shù)據(jù)的示例:
const numbers = [1, 2, 3, 4, 5];
const newNumbers = [...numbers, 6];
console.log(numbers); // 輸出:[1, 2, 3, 4, 5]
console.log(newNumbers); // 輸出:[1, 2, 3, 4, 5, 6]
在這個(gè)示例中,我們使用展開(kāi)運(yùn)算符創(chuàng)建一個(gè)新數(shù)組,在末尾添加一個(gè)新元素,而不修改原始數(shù)組。
10. 使用 Pick
Pick是 TypeScript 的一個(gè)實(shí)用類型,允許我們從現(xiàn)有類型創(chuàng)建新類型,使代碼更易于重用和維護(hù)。它還通過(guò)確保新類型僅包含我們打算使用的屬性來(lái)幫助防止錯(cuò)誤。
以下是一個(gè)示例:
interface User {
name: string;
email: string;
age: number;
isAdmin: boolean;
}
type UserSummary = Pick<User, 'name' | 'email'>;
const user: User = {
name: 'John Doe',
email: 'johndoe@example.com',
age: 30,
isAdmin: false,
};
const summary: UserSummary = {
name: user.name,
email: user.email,
};
console.log(summary); // 輸出:{ name: 'John Doe', email: 'johndoe@example.com' }
在這個(gè)示例中,我們定義了一個(gè)名為User的接口,具有幾個(gè)屬性。然后我們使用Pick實(shí)用類型定義一個(gè)新類型UserSummary,它從User接口中僅選擇name和email屬性。
然后我們創(chuàng)建一個(gè)具有User接口所有屬性的對(duì)象user,并使用name和email屬性創(chuàng)建一個(gè)新的UserSummary類型的對(duì)象summary。
11. 使用 Omit
Omit是 TypeScript 的一個(gè)實(shí)用類型,允許我們從現(xiàn)有類型創(chuàng)建新類型,同時(shí)確保排除某些屬性。當(dāng)處理復(fù)雜接口時(shí),在某些情況下某些屬性可能不需要,這會(huì)很有幫助。它還可以通過(guò)確保某些屬性不會(huì)意外包含來(lái)幫助防止錯(cuò)誤。
以下是一個(gè)示例:
interface User {
name: string;
email: string;
age: number;
isAdmin: boolean;
}
type UserWithoutEmail = Omit<User, 'email'>;
const user: User = {
name: 'John Doe',
email: 'johndoe@example.com',
age: 30,
isAdmin: false,
};
const userWithoutEmail: UserWithoutEmail = {
name: user.name,
age: user.age,
isAdmin: user.isAdmin,
};
console.log(userWithoutEmail); // 輸出:{ name: 'John Doe', age: 30, isAdmin: false }
在這個(gè)示例中,我們定義了一個(gè)名為User的接口,具有幾個(gè)屬性。然后我們使用Omit實(shí)用類型定義一個(gè)新類型UserWithoutEmail,它從User接口中省略email屬性。然后我們創(chuàng)建一個(gè)具有User接口所有屬性的對(duì)象user,并使用name、age和isAdmin屬性創(chuàng)建一個(gè)新的UserWithoutEmail類型的對(duì)象userWithoutEmail。
12. 使用可辨識(shí)聯(lián)合
可辨識(shí)聯(lián)合是 TypeScript 的一個(gè)特性,允許我們根據(jù)特定屬性或?qū)傩越M合對(duì)可以具有不同形狀的類型進(jìn)行建模,并使用switch語(yǔ)句以類型安全的方式處理它們。它是 TypeScript 的一個(gè)強(qiáng)大功能,可以使你的代碼更具表現(xiàn)力和可維護(hù)性。
以下是一個(gè)示例:
interface Square {
kind: 'square';
size: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
interface Triangle {
kind: 'triangle';
base: number;
height: number;
}
type Shape = Square | Circle | Triangle;
function area(shape: Shape) {
switch (shape.kind) {
case 'square':
return shape.size * shape.size;
case 'circle':
return Math.PI * shape.radius * shape.radius;
case 'triangle':
return 0.5 * shape.base * shape.height;
}
}
const square: Square = { kind: 'square', size: 10 };
const circle: Circle = { kind: 'circle', radius: 5 };
const triangle: Triangle = { kind: 'triangle', base: 10, height: 8 };
console.log(area(square)); // 輸出:100
console.log(area(circle)); // 輸出:78.53981633974483
console.log(area(triangle)); // 輸出:40
在這個(gè)示例中,我們定義了三個(gè)接口Square、Circle和Triangle,每個(gè)接口代表一種不同的形狀。然后我們定義了一個(gè)聯(lián)合類型Shape,它可以是Square、Circle或Triangle中的任意一種。
我們定義了一個(gè)函數(shù)area,它接受一個(gè)Shape類型的參數(shù),并使用switch語(yǔ)句根據(jù)形狀的kind屬性計(jì)算其面積。kind屬性被用作可辨識(shí)屬性,因?yàn)樗ㄒ坏貥?biāo)識(shí)了每種形狀類型。
然后我們創(chuàng)建了三個(gè)對(duì)象,每種形狀一個(gè),并使用每個(gè)對(duì)象作為參數(shù)調(diào)用area函數(shù)來(lái)計(jì)算面積。
總結(jié)
這些用于編寫整潔 TypeScript 代碼的技巧可以幫助你編寫更具表現(xiàn)力、可維護(hù)且無(wú)錯(cuò)誤的代碼。通過(guò)使用類型注解、枚舉、可選鏈、空值合并運(yùn)算符、泛型、接口、解構(gòu)、異步/等待、函數(shù)式編程技術(shù)以及各種助手類型(如Pick、Omit和可辨識(shí)聯(lián)合),你可以創(chuàng)建更健壯和可擴(kuò)展的 TypeScript 應(yīng)用程序。
這些技巧還可以幫助你及早捕獲錯(cuò)誤、提高代碼的可讀性并減少需要編寫的樣板代碼量。借助 TypeScript 強(qiáng)大的類型系統(tǒng)和這些技巧,你可以編寫更易于理解和長(zhǎng)期維護(hù)的代碼。