# TypeScript 幼幼班直播筆記(上)

主講:Hello
直播連結:https://youtu.be/Nn9cBo_4FUU
Playground:https://www.typescriptlang.org/play
## TypeScript 是什麼?
TypeScript 屬於進階且較嚴格 (use strict) 的 JavaScript Linter,由微軟進行維護管理,並開源於 Github。
提供 **型別系統**,使用類似 **物件導向** 語法操作原型 (Prototype)。
### 為什麼要用 TypeScript
* 透過 **型別系統** 讓程式碼更容易被維護與協作
* 編譯階段就會跳出錯誤提示,而不是在執行時才出錯
* 避免不必要的錯誤並減少 Bug
* TypeScript 是 JavaScript 的超集,`.js` 檔案可直接重新命名為 `.ts`
:::info
TypeScript 編譯的時候即使報錯了,還是會產生編譯結果,仍然可以使用這個編譯之後的檔案
如果要在報錯的時候終止 js 檔案的產生,可以在 tsconfig.json 中配置 noEmitOnError
:::
<br>
## TypeScript 起手式
### 用 NPM 全域安裝
```javascript=
npm install -g typescript
```
<br>
### 認識編譯流程
#### 1. 在專案內加入 TypeScript
```javascript=
npm install -d typescript
```
#### 2. 加入監控流程
在 `package.json` 裡加入 `watch`
```json=
"scripts": {
"dev": "tsc --watch --preserveWatchOutput"
},
```
#### 3. 可自訂 tsconfig.json
```json=
{
"compilerOptions": {
"outDir": "dist", // 編譯後位置
"target": "ES3" // 編譯支援程度
},
"include": ["src"] // 需要監測的資料夾
}
```
#### 4. 建立對應資料夾
分別建立 `/src/`、`/dist/` 及主要編譯程式 `main.ts`
然後在 `main.ts` 撰寫程式碼
```javascript=
function add(a: number, b: number): number {
return a + b
}
```
接著就能在 `/dist/main.js` 查看編譯後的 JavaScript
```javascript=
function add(a, b) {
return a + b;
}
```
#### 5. 編譯後文件
TypeScript 在編譯過程中會建立三個檔案
1. 原始程式碼:`main.ts`
2. 編譯後檔案:`main.js`
3. 宣告文件(Declaration files):`main.d.ts`
在這個範例裡不會自動產生宣告文件,可透過在 `tsconfig.json` 加入設定來建立
```json=
"compilerOptions": {
"declaration": true
}
```
<br>
## Variables and Values
所有程式語言都有型別,差別在於確立型別的時機 (創造 / 執行),JavaScript 定義型別的時機是在程式 **執行階段**,而 TypeScript 則是在 **創造階段** 就先定義好型別。
以下範例中的 `let` 在 JavaScript 中可以被重新賦值,但在 TypeScript 則會跳出警告提示。
```typescript=
let age = 6;
age = "not a number"
```
:::warning
類型 'string' 不可指派給類型 'number'
:::
<br>
## 字面值型別 Literal Type
前面提到 TypeScript 會在創造階段定義型別,此時稱為 **字面值型別 Literal Type**,而如果沒有定義型別,則會預設為 **any**。
字面值型別會用來限制每個 value 型別,分為以下幾種:
* 字串字面值 (String Literal Types)
* 數字字面值 (Numeric Literal Types)
* 布林字面值 (Boolean Literal Types)
* 列舉字面值 (Enum Literal Types)
以下舉例,先定義 age 型別為 Number,如果給予一個 `a` 字串,透過型別檢測後會出現錯誤提示
```typescript=
let num: Number;
num = 'a';
```
:::warning
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
:::
<br>
## 函式必須明確標註回傳值型別 ⭐
雖然 TypeScript 會自動推斷型別,但避免協作人員在維護上的不確定性,還是必須明確標示參數回傳型別。
```typescript=
function fn(num: number, str: string) { ... }
```
<br>
## 物件 Objects
TypeScript 在定義物件的時候也必須事先定義好每個 Property 的型別。
假設有一個物件 carA,當 carA 建立時,TypeScript 就會自動推斷屬性型別。
```typescript=
let carA = {
make: "Toyota",
model: "Corolla",
year: 2002
}
// 此時可以由提示工具或宣告文件查看內容
declare let carA: {
make: string;
model: string;
year: number;
}
```
### 定義共用型別
除了透過 TypeScript 自動推斷型別外,也可以自己先定義好共用型別。
定義共用型別後,物件內屬性就必須完全一致,若不同則會跳出提示。
```typescript=
type Car = {
make: string,
model: string,
year: number
}
```
```typescript=
let carA: Car = {
make: "Toyota",
model: "Corolla",
year: 2002
}
let carB: Car = {
make: "Toyota",
model: "Corolla",
year: '2002' // 類型 'string' 不可指派給類型 'number'
}
```
### 可選屬性 Optional Properties
有時候每個筆資料物件屬性不一定會完全相同,此時可以使用 Optional Properties 來避免檢測錯誤。
以下範例函式可用來回傳物件資料,如果存在 `car.chargeVoltage` 的話就在字串內加入資料。
如果把 `chargeVoltage?: number` 註解掉的話,這段函式就會跳出 `car.chargeVoltage` 屬性不存在的錯誤,而加上 Optional Properties 的功能就是可以避免這種狀況。
```typescript=
let carA = {
make: "Toyota",
model: "Corolla",
year: 2002
}
let carB = {
make: "Toyota",
model: "Corolla",
year: 2002,
chargeVoltage: 220
}
function printCar(car: {
make: string,
model: string,
year: number,
chargeVoltage?: number
}) {
let str = `${car.make} ${car.model} ${car.year}`;
if (typeof car.chargeVoltage !== 'undefined') {
str += `// ${car.chargeVoltage}`;
}
console.log(str);
}
printCar(carA)
printCar(carB)
```
### 聯合型別 Union Type
Union Type 表示取值可以為多種型別中的一種
和 Optional Properties 不同的是,Union Type 只能對屬性定義型別,不代表此屬性可被選用,如果與宣告的屬性不一致,則會跳出提示。
```typescript=
type Car = {
model: string,
color: string | undefined
}
const carA: Car = {
model: "Corolla"
}
```
:::warning
類型 '{ model: string; }' 缺少屬性 'color',但類型 'Car' 必須有該屬性。
:::
<br>
## 檢查屬性
### Index signatures (索引署名?)
在 TypeScript 如果沒有事先定義物件屬性,就無法存取出物件內容,如以下範例會跳出錯誤提示
```typescript=
const map = { }
map['a'] = 'a'
map['b'] = 'b'
map['c'] = 'c'
```
:::warning
Element implicitly has an 'any' type because expression of type '"a"' can't be used to index type '{}'.
Property 'a' does not exist on type '{}'.
...
:::
如果物件資料是動態產生內容,則可使用 Index signatures 動態產生 key 值
```typescript=
type Dict = { [key: string]: string }
const map: Dict = { }
map['a'] = 'a'
map['b'] = 'b'
map['c'] = 'c'
```
### Array
可使用 `type array = number[]` 來定義陣列中 **值** 的型別,如果不屬於列舉型別,則會跳出提示
```typescript=
type array = (number | string)[]
let arrayA: array = [1, 'a'] // pass
let arrayB: array = [2, {}] // 物件不屬於列舉型別
```
:::warning
Type '{}' is not assignable to type 'number'.
:::
### 元組 Tuples
**Tuples** 可以定義陣列中值的型別,不同於一般變數的宣告方式,透過 Tuple 宣告的陣列長度、位置、型別都必須相同。
也就是說,宣告一組 Tuple 後,操作陣列的最小單位即為一組 Tuple,而這組 Tuple 可以是任何型別。
* **Tuple**
```typescript=
let array: [number, string] = [1, 'a']
let obj: [object] = [{1: 'a'}]
```
* **Tuple Array**
```typescript=
let array: [number, string][]
array = [[1, 'a'], [2, 'b']]
```
* **Tuple Push**
```typescript=
let array: [number, string][] = []
array.push([1, 'a'])
array.push(true) // Error
```
---
* [TypeScript 幼幼班直播筆記(下)](/E96ODFUiQFmPjrcaLojHbQ)
## 參考資料
* [「QnA」Kirby 與 TypeScript 幼幼班](https://www.youtube.com/watch?v=Nn9cBo_4FUU)
* [TypeScript 新手指南](https://willh.gitbook.io/typescript-tutorial/)
* [字面值型別(Literal Types) & 型別別名(Type Alias)](https://ithelp.ithome.com.tw/articles/10221958)