我们将探索十二种用于编写干净代码的 TypeScript 技巧,并通过示例演示它们的工作原理和用途。通过在你自己的 TypeScript 代码中使用这些技巧,小伙伴们可以创建更健壮和可维护的应用程序,这些应用程序更容易推理和调试。
1 — 使用类型注释: TypeScript 是一种静态类型语言,这意味着它允许我们为变量和函数定义类型。使用类型注释有助于在开发过程中尽早发现错误并提高代码的可读性。
以下是 TypeScript 中类型注释的一些栗子:
// 明确指定一个变量的数据类型
let count: number = 0;
// 明确指定一个函数参数和返回值的数据类型
function addNumbers(a: number, b: number): number {
return a + b;
}
// 明确规定类属性的数据类型
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.`;
}
}
在这些示例中,我们使用类型注释来指定变量、函数参数、函数返回值和类属性的数据类型。类型注释写在变量、参数或属性名称之后,用冒号 (:) 分隔,后跟所需的数据类型。
2 — 使用枚举: 枚举是 TypeScript 的一个强大功能,它允许我们定义一组命名常量。它们可以帮助使我们的代码更具可读性和可维护性,并减少由幻数引起的错误的可能性。
以下是如何在 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
。每个常量都有一个相关联的值,可以是字符串或数字。然后我们定义一个函数调用printColor
,它接受一个Color
参数并使用参数值将消息记录到控制台。
当我们使用常量作为参数调用该printColor
函数时Color.Red
,它会将消息“The color is RED”记录到控制台。
3 — 使用可选链接: 可选链接是 TypeScript 的一项功能,它允许我们安全地访问嵌套的属性和方法,而不必担心中间值是 null 还是未定义。这有助于降低运行时错误的可能性并使我们的代码更加健壮。
这是一个如何在 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
属性的对象。然后我们创建两个 type 对象Person
,一个有address
属性,一个没有。
我们使用可选链接来安全地访问对象city
的属性address
,即使该address
属性或其任何子属性未定义或为空。如果链中的任何属性未定义或为 null,则表达式将返回未定义而不是抛出 TypeError。
4 - 使用Nullish Coalescing: Nullish凝聚是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"
在此示例中,我们有三个可能包含空值或未定义值的变量。我们使用 nullish 合并运算符 ( ??
) 来检查值是否为 nullish(null 或 undefined),并在这种情况下提供默认值。
在前两种情况下,变量value1
和value2
分别为 null 或 undefined,因此返回默认值。在第三种情况下,变量value3
包含一个非空/非未定义的值,因此返回该值而不是默认值。
5 — 使用泛型: 泛型是 TypeScript 的一项强大功能,它允许我们编写适用于不同类型的可重用代码。它们可以帮助减少代码重复并提高代码的可维护性。
以下是如何在 TypeScript 中使用泛型的栗子:
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("hello"); "hello"
let output2 = identity<number>(42); // 42
在这个例子中,我们定义了一个调用的函数identity
,它接受一个类型参数T
并返回与传入的相同类型的值。该函数可以处理任何类型的数据,并且在调用该函数时指定实际数据类型。
然后,我们使用identity
两种不同的数据类型调用该函数:字符串和数字。该函数返回与传入的值类型相同的值,因此output1
是 typestring
和output2
type number
。
6 — 使用Interfaces: interface是 TypeScript 的另一个强大功能,可以帮助我们编写干净且可读的代码。它们允许为类、对象或函数定义契约,这可以帮助我们避免常见错误并使代码更加自文档化。
以下是如何在 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 对象的interface,包括一个firstName
andlastName
属性和一个可选age
属性。然后我们定义一个名为的函数sayHello
,该函数将一个Person
对象作为参数并将问候语打印到控制台。
我们创建两个与界面形状匹配的对象Person
,并将它们传递给sayHello
函数。该函数能够访问每个对象的firstName
和lastName
属性,并age
在将其打印到控制台之前检查该属性是否存在。
7 — 使用解构: 解构是一种简便语法,允许我们从数组和对象中提取值。它可以使代码更具可读性和简洁性,并减少因未对齐变量名称而导致错误的可能性。
以下是如何在 TypeScript 中使用解构的一些栗子:
对象解构:
Object destructuring:
let person = { firstName: "John", lastName: "Doe", age: 30 };
let { firstName, lastName } = person;
console.log(firstName); // "John"
console.log(lastName); // "Doe"
在此示例中,我们创建了一个具有三个属性的对象person
。然后我们使用对象解构来提取firstName
和lastName
属性并将它们分配给同名变量。这使我们能够使用更少的代码更轻松地访问这些属性
数组解构:
let numbers = [1, 2, 3, 4, 5];
let [first, second, , fourth] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(fourth); // 4
在这个例子中,我们创建了一个数字数组,并使用数组解构来提取第一、第二和第四个元素并将它们分配给变量。我们使用解构模式中的空槽跳过第三个元素。这使我们能够使用更少的代码更轻松地访问数组的特定元素。
解构也可以与函数参数一起使用,允许我们从作为参数传递的对象中提取特定值:
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
,该函数使用函数参数中的解构语法将具有firstName
和lastName
属性的对象作为参数。然后我们传入一个person
对象,该greet
函数能够提取firstName
和lastName
属性并在控制台日志语句中使用它们。
8 — 使用 Async/Await: Async/await 是 TypeScript 的一项强大功能,它允许我们编写同步代码和异步代码。它可以帮助提高代码的可读性并减少由回调地狱引起的错误的可能性。
以下是如何在 TypeScript 中使用 async/await 的栗子:
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);
});
在此示例中,我们定义了一个async
调用的函数getData
,它使用关键字向 API 发出fetch
请求并等待响应await
。然后我们使用 方法解析响应json()
并再次使用 等待结果await
。最后,我们返回data
对象。
然后我们调用该getData()
函数并使用then()
方法来处理返回的数据,或者catch()
使用方法来处理可能发生的任何错误。
9 — 使用函数式编程技术: 函数式编程技术,例如不变性、纯函数和高阶函数,可以帮助我们编写干净且可维护的代码。它们可以帮助减少副作用并使代码更具可预测性和可测试性。
纯函数: 纯函数是没有副作用的函数,并且在给定相同输入的情况下始终返回相同的输出。纯函数可以更轻松地对代码进行推理,并有助于防止错误。
下面是一个纯函数的栗子:
function add ( a: number , b: number ): number {
return a + b;
}
高阶函数: 高阶函数是一种将一个或多个函数作为参数或返回一个函数作为其结果的函数。高阶函数可用于创建可重用代码并简化复杂逻辑。
下面是一个高阶函数的栗子:
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); // Output: [2, 4, 6, 8, 10]
在此示例中,该map
函数将一个数组和一个函数作为参数,并将该函数应用于数组中的每个元素,并返回一个包含结果的新数组。
不可变数据: 不可变数据是在创建后无法更改的数据。在函数式编程中,强调不变性以防止副作用并使代码更易于推理。下面是使用不可变数据的示例:
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]
在此示例中,我们使用扩展运算符创建一个新数组,并在末尾附加一个新元素,而不修改原始数组。
10 - 使用Pick助手: Pick助手是TypeScript的一个实用类型,它允许我们从现有的类型中创建新的类型,使其更容易重复使用和维护代码。它还有助于通过确保新类型只包括我们打算使用的属性来防止错误。
这是一个栗子:
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
。UserSummary
然后我们使用实用程序类型定义一个新类型Pick
,它只从接口中选择name
和属性。email``User
然后我们创建一个具有接口user
所有属性的对象User
,并使用name
和email
属性创建一个新的summary
类型对象UserSummary
。
11 - 使用Omit Helper: Omit helper是TypeScript的一个实用类型,它允许我们从现有的类型中创建新的类型,同时确保某些属性被排除。这在处理复杂的接口时很有帮助,因为某些属性在某些情况下可能不需要。它还可以通过确保某些属性不被意外地包括在内来帮助防止错误。
这是一个栗子:
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
。UserWithoutEmail
然后我们使用实用程序类型定义一个新类型Omit
,它email
从User
接口中省略了属性。
然后,我们创建一个user
具有接口所有属性的对象User
,并使用name
、age
和isAdmin
属性创建一个userWithoutEmail
类型为 的新对象UserWithoutEmail
。
12 — 使用可区分联合:可区分联合是 TypeScript 的一项功能,它允许我们根据特定属性或属性组合对可以呈现不同形状的类型进行建模,并使用 switch 语句以类型安全的方式处理它们。它是 TypeScript 的一个强大功能,可以使我们的代码更具表现力和可维护性。
这是一个栗子:
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
,每个接口代表不同的形状。然后我们定义一个联合类型Shape
,它可以是 a Square
、 aCircle
或 a Triangle
。
我们定义了一个函数area
,该函数将 type 的形状Shape
作为参数,并使用 switch 语句根据其种类计算形状的面积。该kind
属性用作区分属性,因为它唯一标识每种形状。
然后我们创建三个对象,每个对象对应一种形状,并area
以每个对象作为参数调用函数来计算面积。
总之,这些用于编写干净代码的 TypeScript 技巧可以帮助我们编写更具表现力、可维护性和无错误的代码。通过使用类型注释、枚举、可选链、无效合并、泛型、接口、解构、异步/等待、函数式编程技术以及各种帮助程序类型(如 、 和可区分的联合),创建更健壮和可扩展的 TypeScript 应用Pick
程序Omit
。
这些技巧还可以帮助我们及早发现错误、提高代码的可读性并减少需要编写的样板代码量。借助 TypeScript 的强类型系统和这些技巧,编写更易于推理和随时间维护的代码。