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