

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 遵循 TypeScript 最佳实践
<a name="typescript-best-practices"></a>

TypeScript 是一种扩展功能的语言 JavaScript。它是一种强类型化且面向对象的语言。您可以使用 TypeScript 来指定在代码中传递的数据类型，并能够在类型不匹配时报告错误。本节概述了 TypeScript 最佳实践。

## 描述您的数据
<a name="describe-data"></a>

您可以使用 TypeScript 来描述代码中对象和函数的形状。使用 `any` 类型相当于选择不对变量进行类型检查。建议避免在代码中使用 `any`。下面是一个例子。

```
type Result = "success" | "failure"
function verifyResult(result: Result) {
    if (result === "success") {
        console.log("Passed");
    } else {
        console.log("Failed")
    }
}
```

## 使用枚举
<a name="use-enums"></a>

您可以使用枚举定义一组命名常量以及可在代码库中重用的标准。我们建议您在全局级别导出一次枚举，然后让其他类导入并使用枚举。假设您要创建一组可能的操作来捕获代码库中的事件。 TypeScript 提供数字和基于字符串的枚举。以下示例使用了枚举。

```
enum EventType {
    Create,
    Delete,
    Update
}

class InfraEvent {
    constructor(event: EventType) {
        if (event === EventType.Create) {
            // Call for other function
            console.log(`Event Captured :${event}`);
        }
    }
}

let eventSource: EventType = EventType.Create;
const eventExample = new InfraEvent(eventSource)
```

## 用户接口
<a name="use-interfaces"></a>

接口是类的合同。如果您创建合同，则用户必须遵守合同。在以下示例中，使用接口来实现 `props` 标准化，确保调用方在使用该类时提供预期的参数。

```
import { Stack, App } from "aws-cdk-lib";
import { Construct } from "constructs";

interface BucketProps {
    name: string;
    region: string;
    encryption: boolean;

}

class S3Bucket extends Stack {
    constructor(scope: Construct, props: BucketProps) {
        super(scope);
        console.log(props.name);

    }
}
const app = App();
const myS3Bucket = new S3Bucket(app, {
    name: "amzn-s3-demo-bucket",
    region: "us-east-1",
    encryption: false
})
```

某些属性只能在首次创建对象时修改。您可以在属性名称前加上 `readonly` 进行指定，如下面的示例所示。

```
interface Position {
    readonly latitude: number;
    readonly longitute: number;
}
```

## 扩展接口
<a name="extend-interfaces"></a>

扩展接口可以减少重复，因为您不必在接口之间复制属性。此外，代码读取器也能轻松理解应用程序中的关系。

```
 interface BaseInterface{
    name: string;
  }
  interface EncryptedVolume extends BaseInterface{
      keyName: string;
  }
  interface UnencryptedVolume extends BaseInterface {
      tags: string[];
  }
```

## 避免使用空接口
<a name="empty-interfaces"></a>

我们建议您避免使用空接口，因为它们会带来潜在风险。在以下示例中，有一个名为的空接口`BucketProps`。`myS3Bucket1` 和 `myS3Bucket2` 对象都是有效的，但它们遵循不同的标准，因为接口不强制执行任何合同。以下代码将编译和打印属性，但这会在您的应用程序中产生不一致。

```
interface BucketProps {}

class S3Bucket implements BucketProps {
    constructor(props: BucketProps){
        console.log(props);
    }
}

const myS3Bucket1 = new S3Bucket({
    name: "amzn-s3-demo-bucket",
    region: "us-east-1",
    encryption: false,
});

const myS3Bucket2 = new S3Bucket({
    name: "amzn-s3-demo-bucket",
});
```

## 使用工厂
<a name="use-factories"></a>

在抽象工厂模式中，接口负责创建相关对象的工厂，而无需明确指定其类。例如，您可以创建一个 Lambda 工厂来创建 Lambda 函数。您不是在构造中创建新的 Lambda 函数，而是将创建过程委托给工厂。有关此设计模式的更多信息，请参阅 Refactoring.G [ur TypeScript u 文档中的抽象工厂](https://refactoring.guru/design-patterns/abstract-factory/typescript/example)。

## 对属性使用解构
<a name="destructuring-props"></a>

 ECMAScript 6 (ES6) 中引入的解构 JavaScript 功能使您能够从数组或对象中提取多段数据，并将它们分配给它们自己的变量。

```
const object = {
    objname: "obj",
    scope: "this",
};

const oName = object.objname;
const oScop = object.scope;

const { objname, scope } = object;
```

## 定义标准命名约定
<a name="naming-conventions"></a>

强制执行命名约定可以保持代码库的一致性，并在考虑如何命名变量时减少开销。我们建议执行下列操作：
+ 对变量和函数名称使用 camelCase。
+ 使用 UPPER\_CASE 作为全局常量，以清楚地指示不可变的编译时值。
+  PascalCase 用于类名和接口名。
+ 对接口成员使用 camelCase。
+  PascalCase 用于类型名称和枚举名称。
+ 用 camelCase 命名文件（例如，`ebsVolumes.tsx` 或 `storage.ts`）

以下是这些推荐的命名约定的示例：

```
// Variables and functions
const userName = 'john';
function getUserData() { }

// Global constants
const MAX_RETRY_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';

// Classes and interfaces
class DatabaseConnection { }
interface UserProfile { }

// Types and enums
type ResponseStatus = 'success' | 'error';
enum HttpStatusCode { }
```

## 不要使用 var 关键字
<a name="var-keyword"></a>

该`let`语句用于在中声明局部变量 TypeScript。它与关键字类似，但与`var`关键字相比，它在范围上`var`有一些限制。在 `let` 块中声明的变量只能在该块中使用。`var`关键字不能是块作用域的，这意味着它可以在特定块（由`{}`）之外访问，但不能在定义它的函数之外访问。您可以重新声明和更新`var`变量。最佳做法是避免使用`var`关键字。

## 考虑使用 and Pr ESLint ettier
<a name="eslint-prettier"></a>

ESLint 静态分析您的代码以快速发现问题。您可以使用 ESLint 创建一系列断言（称为 *lint 规则*），这些断言定义代码的外观或行为。 ESLint 还提供了自动修复器建议，可帮助您改进代码。最后，您可以使用 ESLint 从共享插件中加载 lint 规则。

Prettier 是一个知名的代码格式化程序，它支持各种不同的编程语言。您可以使用 Prettier 设置代码样式，避免手动格式化代码。安装后，您可以更新 `package.json` 文件并运行 `npm run format` 和 `npm run lint` 命令。

以下示例向您展示了如何为项目启用 ESLint 和使用 Prettier 格式化程序。 AWS CDK 

```
"scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk",
    "lint": "eslint --ext .js,.ts .",
    "format": "prettier --ignore-path .gitignore --write '**/*.+(js|ts|json)'"
}
```

## 使用访问修饰符
<a name="access-modifiers"></a>

中的 private 修饰符仅 TypeScript 限于同一个类的可见性。在属性或方法中添加 private 修饰符后，就可以在同一类中访问该属性或方法。

public 修饰符允许从所有位置访问类属性和方法。如果您没有为属性和方法指定任何访问修饰符，则默认情况下，它们将采用 public 修饰符。

protected 修饰符允许在同一类和子类中访问类的属性和方法。当您希望在 AWS CDK 应用程序中创建子类时，请使用 protected 修饰符。

## 使用实用程序类型
<a name="utility-types"></a>

中的@@ *实用程序类型* TypeScript 是预定义的类型函数，用于对现有类型执行转换和操作。这可以帮助您基于现有类型创建新类型。例如，您可以更改或提取属性，将属性设为可选或必填属性，或者创建类型的不可变版本。通过使用实用程序类型，您可以定义更精确的类型，并在编译时捕获潜在的错误。

### 部分 <Type>
<a name="partial-type"></a>

`Partial`将输入类型的所有成员标记`Type`为可选。此实用程序返回一个表示给定类型的所有子集的类型。以下是 `Partial` 的示例。

```
interface Dog {
  name: string;
  age: number;
  breed: string;
  weight: number;
}

let partialDog: Partial<Dog> = {};
```

### 必填项 <Type>
<a name="required-type"></a>

`Required`恰恰相反`Partial`。它使输入类型的所有成员都`Type`不是可选的（换句话说，是必需的）。以下是 `Required` 的示例。

```
interface Dog {
  name: string;
  age: number;
  breed: string;
  weight?: number;
}

let dog: Required<Dog> = { 
  name: "scruffy",
  age: 5,
  breed: "labrador",
  weight: 55 // "Required" forces weight to be defined
};
```