---
# System prepended metadata

title: "TypeScript \U0001F596"
tags: [TypeScript]

---

# TypeScript 🖖

######  Tags: `TypeScript`

TypeScript 是 JavaScript 的超集 （ TS 有 JS 所有的功能 與 自己的技術，主打技術 － 型別系統，是一門強型別的語言，透過事先定義型別的模式，彌補了 JavaScript 太過自由導致後續容易發生型別不對應產生的 bug；TypeScript 也支援編譯成不同版本的 JavaScript，能夠在各個瀏覽器執行。

TypeScript 在 State of JS 2020 獲頒 最多人使用獎，高使用率與高滿意度讓大家蜂擁而上的學習這門技術。

## 為何選擇 TS ?

 - 型別系統
   `開發期間即定義好型別，避免因為沒有定義型別而產生無預期的錯誤。`
   `e.g. JS => 1 + '1' = '11'，在 TS 若是先定義好 param 型別為 number，即可避免後續問題。`
 - 自由設定要編譯成什麼版本的 JS
 - 撰寫期間即提示錯誤
 - 前端框架支援 （ e.g. Vue 3 
 - 多個 IDE 支援
 - 社群日漸成熟

## 可能會遇到的難關

 - 對於寫 JS 的前端工程師有一定的學習成本
 - 開發時要多花一些成本，但對於後續的維護是值得的。

## 安裝
```shell
npm install -g typescript
```

```shell
tsc -v
```


## 開始撰寫
> 1. 撰寫一段 TS 的語法

```typescript
// index.ts
function sum(a: number, b: number):number {
  return a + b
}
```

> 2.1 透過 `tsc <路徑/檔案>` 編譯 `指定的檔案`
```shell
tsc ./index.ts
```
OR

> 2.2 透過 `tsc` 編譯當前路徑下的所有 TS 檔
```shell
tsc
```

> 3. 自動產生出編譯後的 JS
```javascript
// index.js
"use strict";
function sum(a, b) {
    return a + b;
}
```

&nbsp;

> 補充：透過 `--watch / -w` 自動偵測異動並進行編譯
```shell
tsc -w
```


## 高速開發 ts - 透過 nodemon & concurrently 達成
> 1. 裝一下該裝的東東

```shell
npm init -y // 初始化一個 node 專案

tsc --init // 初始化建立 tsconfig.json

npm i nodemon concurrently --save-dev // 安裝 nodemon & concurrently
```

> 2. 建立 `dist` 及 `src` 兩個資料夾， `src` 用來放 TS， `dist` 用來放編譯後的 JS

> 3. 將 `tsconfig.json` 中的 `outDir` 及 `rootDir` 設定好
```json
…
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files.
Use to control the output directory structure with --outDir. */
…
```

> 4. 設定 `package.json` 中的 `script`
```json
…
"scripts": {
    "start:build": "tsc -w",
    "start:run": "nodemon ./dist/index.js",
    "start": "concurrently npm:start:*"
},
…
```
:::info
可以自行獨立 run `tsc -w` 及 `nodemon ./dist/index.js`看看，再體驗 `concurrently` 帶來的好處。
:::
> 5. 開始執行
```shell
npm start
```
![](https://i.imgur.com/LncyCwL.png)

託 `concurrently` 所賜，此時我們可以發現若是更新 TS 後儲存，會立刻幫我進行編譯 ( `tsc -w` ) 與執行 ( `nodemon ./dist/index.js` )。

## 資料型別

TS 的資料型別分為 `原始型別` 與 `物件型別`。

### 原始型別
 - number
 - string
 - boolean
 - undefined
 - null
 - symbol

### 任意值

若變數在定義時，冠上型別 `any` ，此變數可以為任意型別且不報錯。
```typescript
let val: any = '123'

val = 50
```
若變數在定義時，並沒有指定型別，TypeScript 會將此變數視為 `any` 型別。
```typescript
let val // 在 typescript 眼裡 ->  let val: any

val = 1

val = '123'
```

> 太依賴型別 any 並不是一件好事，斟酌使用。 

### 型別推論

若變數在定義時，並沒有指定型別但有指定 val，TS 會自行推論出此變數的型別，若之後修改此變數時型別錯誤則會報錯。

```typescript
let val = '123' // 在 typescript 眼裡 ->  let val: string = '123'

val = 1 // Type 'number' is not assignable to type 'string'.
```

### 聯合型別 / Union Types

型別可以定義多個，使用 `|` 來區隔。

```typescript
let val: string | number = '123'

val = 1 // work !

val = false // Type 'boolean' is not assignable to type 'string | number'.
```

上例 val 可以為 string 、 number，若我們要事先撰寫程式對 val 進行處理，必須使用這些型別共同有的屬性。

```typescript
function getLength(val: string | number): number {
    return val.length
}
// Property 'length' does not exist on type 'string | number'.  Property 'length' does not exist on type 'number'.
```

可以這麼解決：
```typescript
function getLength(val: string | number): number {
    return val.toString().length
}
```

我們也可以設定好聯合型別提供使用
```typescript
type numOrString = number | string

let val: numOrString

val = 10

val = 'abc'
```


### 物件型別

你可以使用以下方式簡易的建立物件：

```typescript
let obj: object = {
    id: 1,
    name: 'chenyo',
}
```

也可以透過 interface 來定義物件型別：

#### 確定屬性

確定屬性就是定義完 interface 後確定會有的欄位，定義了幾組 key value，物件就必須符合定義，有多有少都會報錯！

```typescript
interface Customer {
    phoneid: number
    name: string
    gender: string
    time: Date
}

const chenyo: Customer = {
    phoneid: 123456789,
    name: 'chenyo',
    gender: 'male',
    time: new Date("2021-07-26")
}
```

> 可以在 interface 的名稱前面加大寫 I，避免把變數與介面搞混。 ( e.g. I_Customer / ICustomer

> interface 內個別屬性定義後面不須加 `,` ，但加了好像也沒關係 (? 


#### 可選屬性 / Optional Properties

在 key 後面加上 `?`，此欄位即為選填。

```typescript
interface Customer {
    phoneid: number
    name: string
    gender: string
    time: Date
    tips?: string  // 不見得要有此組 key value
}

const chenyo: Customer = {
    phoneid: 123456789,
    name: 'chenyo',
    gender: 'male',
    time: new Date("2021-07-26")
}
```

#### 任意屬性

`[propName: key 型別]: value 型別` 

```typescript
interface Customer {
    phoneid: number
    name: string
    gender: string
    time: Date
    [propName: string]: any
}

const chenyo: Customer = {
    phoneid: 123456789,
    name: 'chenyo',
    gender: 'male',
    time: new Date("2021-07-26"),
    feeling: 'happy'
}
```

#### 唯獨屬性

在 interface 建立時，在 key 前面加上 `readonly`

```typescript
interface Customer {
    readonly phoneid: number
    name: string
    gender: string
    time: Date
}

const chenyo: Customer = {
    phoneid: 123456789,
    name: 'chenyo',
    gender: 'male',
    time: new Date("2021-07-26")
}

chenyo.phoneid = 123 // Cannot assign to 'phoneid' because it is a read-only property.
```

### 陣列型別

以下三種方式都可以建立陣列，但第三種使用 interface 的較少用。
```typescript
let array: number[]

array = [1, 2, 3, 4]
```

```typescript
let array: Array<number>

array = [1, 2, 3, 4]
```

```typescript
interface numArray {
    [index: number]: number
}

let array: numArray = [1, 2, 3]
```

### 函式型別

可以透過 `函式陳述式` 或是 `函式表達式` 來進行函式的宣告。

輸入及輸出都要進行型別的管制！

#### 函式陳述式 Function Statement

```typescript

function stringSum(x: string, y: string, z: string): string {
    return x + y + z
}
```

#### 函式表達式 Function Expression

```typescript
const stringSum = (x: string, y: string, z: string): string => x + y + z
```

函式的引數定義了幾個，呼叫函式時就必須代幾個，或多或少都不行，除非有預先設定了 `可選引數` 或 `預設引數`。

#### 可選引數

與前述可選屬性一樣，在 key 的後面加上 `?`。

```typescript
const stringSum = (x: string, y: string, z?: string): string => x + y + z 

console.log(stringSum('Hello ', 'World')) // Hello World
```

> 可選引數必須放在引數的最後面，否則會報錯！

```typescript
const stringSum = (x: string, y?: string, z: string): string => x + y + z 

// A required parameter cannot follow an optional parameter.
```

#### 預設引數

若是為引數添加預設值，呼叫函式時可不填。

```typescript
const stringSum = (x: string, y: string, z: string = '!'): string => x + y + z 

console.log(stringSum('Hello ', 'World')); // Hello World!
```

當然也可以所有的引數都定義預設值，直接呼叫函式不帶引數也可以運行

```typescript
const stringSum = (x: string = 'Hello', y: string = ' World' , z: string = '!'): string => x + y + z 

console.log(stringSum());
```

雖說預設引數並沒有像可選引數一樣有著必須放在確定引數的後方，但可能還是會出錯，如下：

```typescript
const stringSum = (x: string = 'Hello', y: string , z: string = '!'): string => x + y + z 

console.log(stringSum('World'); // not work
```

這邊只有 y 並沒有預設引數，若是想印出 'Hello World!' 則必須呼叫：
```typescript
console.log(stringSum('Hello', 'World'); // work !
```

#### 剩餘引數

使用 `ES6` 的 `...rest` 來獲取函式的剩餘引數。

```typescript
function sayIt(first: string, second: string, ...items: number[]): void {
    console.log('first param:', first);
    console.log('second param:', second);
    items.forEach(item => {
        console.log(item);
    });
}

sayIt('我是第一個', '我是第二名', 5, 4, 8, 7)

// first param: 我是第一個
// second param: 我是第二名
// 5
// 4
// 8
// 7
```

若是在 `tsconfig.json` 中把 `target` 設定為 ES6 以下的版本時，ES6 的 `...rest` 會被編譯成以下：

```javascript
"use strict";
function sayIt(first, second) {
    var elses = [];
    for (var _i = 2; _i < arguments.length; _i++) {
        elses[_i - 2] = arguments[_i];
    }
    console.log('first param:', first);
    console.log('second param:', second);
    elses.forEach(function (item) {
        console.log(item);
    });
}
sayIt('我是第一個', '我是第二名', 5, 4, 8, 7);
```

#### 過載

過載能讓我們依照不同類型的引數，執行到不同的處理流程。

如下例子，我們無法確定傳入的參數型別，這也導致我們無法明確知道結果。

最後我們針對 result 進行 split，看似正常，但 `numOrString` 與 `number` 並不存在 split 的方法，而導致報錯。

```typescript
type numOrString = string | number

function sum(x: numOrString, y: numOrString): numOrString {
    if (typeof x === 'string' || typeof y === 'string') {
        return x.toString() + y.toString()
    } else return x + y
}

const result = sum('Hello', ' World!');

console.log(result.split(''));
// Property 'split' does not exist on type 'numOrString'.
// Property 'split' does not exist on type 'number'.
```

這邊我們為此函式設定多組型別引數來進行函式過載。

```typescript
type numOrString = string | number

function sum(x: number, y: number): number
function sum(x: number, y: numOrString): string
function sum(x: numOrString, y: number): string
function sum(x: numOrString, y: numOrString): string
function sum(x: numOrString, y: numOrString): numOrString {
    if (typeof x === 'string' || typeof y === 'string') {
        return x.toString() + y.toString()
    } else return x + y
}

const result = sum('Hello', ' World!');

console.log(result.split(''));
// [ 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!' ]
```


