

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 遵循 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.Guru 文件中的 [TypeScript 中的抽象工廠](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\$1CASE 用於全域常數，以清楚指出不可變的編譯時間值。
+ 對類別名稱和介面名稱使用 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`關鍵字。

## 考慮使用 ESLint 和 Prettier
<a name="eslint-prettier"></a>

ESLint 可靜態分析您的程式碼以快速發現問題。您可以使用 ESLint 建立一系列聲明 (稱為 *lint 規則*) 來定義程式碼的外觀或行為方式。ESLint 還提供自動修復程式建議來協助您改進程式碼。最後，您可以使用 ESLint 從共用外掛程式載入 lint 規則。

Prettier 是一個知名的程式碼格式器，支援多種不同的程式設計語言。您可以使用 Prettier 設定程式碼樣式，以便避免手動格式化程式碼。安裝後，您可以更新您的 `package.json` 檔案並執行 `npm run format` 和 `npm run lint` 命令。

下列範例說明如何為您的 AWS CDK 專案啟用 ESLint 和 Prettier 格式工具。

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

## 使用 access 修飾詞
<a name="access-modifiers"></a>

TypeScript 中的 private 修飾詞僅將可見性限制為相同類別。將 private 修飾詞新增至屬性或方法時，您可以在相同類別內存取該屬性或方法。

public 修飾詞允許從所有位置存取類別屬性和方法。如果您未指定屬性和方法的任何存取修飾詞，它們預設會採用公有修飾詞。

protected 修飾詞允許在相同類別和子類別內存取類別的屬性和方法。當您預期在 AWS CDK 應用程式中建立子類別時，請使用受保護的修飾詞。

## 使用公用程式類型
<a name="utility-types"></a>

TypeScript 中的*公用程式類型*是預先定義的類型函數，可在現有類型上執行轉換和操作。這可協助您根據現有類型建立新的類型。例如，您可以變更或擷取屬性、將屬性設為選用或必要，或建立不可變的類型版本。透過使用公用程式類型，您可以定義更精確的類型，並在編譯時擷取潛在的錯誤。

### 部分＜類型>
<a name="partial-type"></a>

`Partial` 會將輸入類型的所有成員標記為`Type`選用。此公用程式會傳回代表指定類型之所有子集的類型。以下是 `Partial` 的範例。

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

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

### 必要＜類型>
<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
};
```