---
# System prepended metadata

title: TypeScript+React(Max)第 2 節：TypeScript Basics & Core Concept
tags: [TypeScript, Udemy]

---

# TypeScript+React(Max)第 2 節：TypeScript Basics & Core Concept

> Udemy課程：[React & TypeScript - The Practical Guide](https://www.udemy.com/course/react-typescript-the-practical-guide/)

`20240211Sun.~20240213Tue.`
:::danger
2-12 一個變數多個型別
2-13 物件型別
2-14 陣列型別
2-15 function參數及return值的型別
2-16 function type
2-17 "type" 關鍵字 > 自定義型別
2-18 "interface" 關鍵字 > 定義物件型別
2-19 大比較：extends vs. implements / type vs. interface
2-21 Literal Types
:::

## 2-8. TypeScript Setup & Using TypeScript
> TypeScript：[官方網站](https://www.typescriptlang.org/)

TypeScript並不會在browser中執行，因此在進入browser前必須進行compile成JS code，所以在使用TS前就要先下載TS的compiler。
![2024-02-12 10-38-49 的螢幕擷圖](https://hackmd.io/_uploads/rkjT4bDi6.png)

* 透過npm 下載：
    使用npm可先下載nodejs，另外官網有更清楚的下載方式。
    * 在個別專案下，下載TS
        ```javascript!
        npm install typescript --save-dev
        ```
    * 在全域環境下，下載TS
        ```javascript!
        npx tsc
        或
        npm install -g typescript
        ```

****

## 2-9. Working with Types: Type Inference & Explicit Type Annotations

先來看看簡單的範例，首先對username賦值一個字串"Max"，這時若再assign數字給username，在TypeScript終究會出現錯誤的顯示。
![image](https://hackmd.io/_uploads/S1EoiWDia.png)

如果想看該變數是什麼型態，可以在vscode中把鼠標hover至該變數之上，就能看出是什麼型態了。
![image](https://hackmd.io/_uploads/Hkv12Zwoa.png)

我們可以看見上方，當我們先賦值字串給username變數，則username的型態即為"string"。不過當我們什麼都不賦予給username，則代表他可以為任意型態"any"。
![image](https://hackmd.io/_uploads/BJgs3bvi6.png)

但是有時候，我們雖然在一開始沒有賦值，但希望可以讓其他開發者知道這個變數為某個型態時，我們就能使用TS的syntax「冒號」加上「型態」，來表示這個變數應該為何種型態。
```typescript!
let username: string;
username = "Max";
```

****

## 2-12. Combining Types Union Types (Alternative Types)
有時一個變數可能同時會利用到string與number之類的，多個型別來賦值，這時若照原本的方法去賦值，在TS中必定會出錯。
```typescript!
let userID = "abc123";
userID = 123;
```
那怎麼辦呢？在TS中，若想讓一個變數同時擁有多個型別，可以利用"|"(pipe)符號來賦值，如下方所示：
```typescript!
let userID: string | number = "abc123";
userID = 123;
```

****

## 2-13. Working with Object Types
再來是object，我們可能會想照先前的方法寫出如下的程式碼：
```typescript!
let user: object;
user = {
    name: "Max",
    age: 34,
    isAdmin: true,
    _id: "abc123"
}
```

但是這樣並不嚴謹，因為這個時候即便我只寫出`user = {}`，TS也會判定這是沒有問題的。

不過我們希望user物件中的key value結構也能定義好型別，那麼該怎麼做呢？
```typescript!
let user: {
    name: string,
    age: number,
    isAdmin: boolean,
    _id: string | number
}
```
此時當我們寫出`user = {}`，TS便會報錯。

![2024-02-12 11-37-57 的螢幕擷圖](https://hackmd.io/_uploads/H1le2fMDip.png)

在這裡要注意，在定義型別時，跟我們在賦值給物件時長得很像，不過定義型別的syntax都是用「冒號＋型別」！

![image](https://hackmd.io/_uploads/BJjK7fDsa.png)

****

## 2-14. Working with Array Types
陣列型別在TypeScript中較為特別，有兩點值得注意：
1. 陣列型別表達必須大寫開頭Array
2. 陣列型別表達後必須用`<>`符號包覆陣列中所放置的型別
    e.g.
    ```typescript!
    let hobbies: Array<string>;
    hobbies = ["Sports", "Cooking", "Reading"];
    ```
    另一種表達方式：
    ```typescript!
    let hobbies: string[];
    hobbies = ["Sports", "Cooking", "Reading"];
    ```

****

## 2-15. Adding Types to Functions - Parameter & Return Value Types
之前肯定都學過當我們利用"const"宣告變數時，有兩個注意事項：
1. 必須要賦值
    ![image](https://hackmd.io/_uploads/rkzePzDja.png)
2. 之後不能再另外對該變數賦值
    ![image](https://hackmd.io/_uploads/H1kWvzwo6.png)

也因此當我們是用"const"宣告變數的話，代表著我們不必表達他們的型態，因為這個變數被用"const"宣告後，既不能改變，它所代表的值也在宣告的當下就被決定好了。

另外，在function中給予的參數也可以先定義好型別，作法其實與前面幾節大同小異，這邊直接來看個範例：
```typescript!
function add(a: number, b: number){
    const result = a + b;
    console.log(result);
}
```

從上方程式碼可以看見，這個add function是不會return值的，所以當我們想要定義這個function return值的型別時可以用"void"（空白）型別，而定義return值型別的位置則位於放置參數括號的後方。
```typescript!
function add(a: number, b: number): void{
    const result = a + b;
    console.log(result);
}
```
所以若我們的function會return值，那麼當然就是把"void"改寫成想要的型別即可，不過其實不對return值定義型別也不會報錯的。
```typescript!
function add(a: number, b: number): number{
    const result = a + b;
    return result;
}
```

****

## 2-16. Defining Function Types
再來要針對function type做說明。首先，我們先看看以下程式碼：
```typescript!
function add(a: number, b: number): number{
    const result = a + b;
    return result;
}

function calculate(a: number, b: number, calcFn){
    calcFn(a, b);
}

calculate(3, 5, add);
```

這樣的程式碼並不會報錯，但是我們希望可以定義好calculate參數中"calc function"的型別，他是一個function，所以function type該如何定義？其實可以從上一章節大概知道一些想法，這裡直接看看怎麼把上方程式碼增加function type。

```typescript!
function add(a: number, b: number): number{
    const result = a + b;
    return result;
}

function calculate(
    a: number, 
    b: number, 
    calcFn: (a: number, b: number) => number
){
    calcFn(a, b);
}

calculate(3, 5, add);
```

一樣就是利用TypeScript中的syntax「冒號＋型別」。
![image](https://hackmd.io/_uploads/Bylm2zwip.png)

****

## 2-17. Creating Custom Types / Type Aliases
但從上一章節定義function type，會發現這麼簡單的一個function type就要定義成那麼長，那如果程式變得更加複雜怎麼辦呢？

於是這裡我們就可以利用TypeScript中的關鍵字"type"來自定義型別，但更準確的來說，其實就是用別名來替代那些又臭又長的型別們。

以上一章節為例，我們另外定義一個type名叫AddFn來替代我們calcFn後面那串funciton type。
```typescript!
function add(a: number, b: number): number{
    const result = a + b;
    return result;
}

type AddFn = (a: number, b: number) => number;

function calculate(
    a: number, 
    b: number, 
    calcFn: AddFn
){
    calcFn(a, b);
}

calculate(3, 5, add);
```
![image](https://hackmd.io/_uploads/BkXH0zvoT.png)

而這種作法，除了可以讓function參數中看起來更清楚簡潔外，我們也可以「重複使用」這個另外定義的型別。

例如說我們前面章節曾經提到，可能會有一個變數多個型別的狀況，這個時候我們就能針對這種多個型別另外定義。

```typescript!
type StringOrNum = string | number;
//重複使用這個自定義型別
let userID: StringOrNum;

let user: {
    userName: string,
    age: number,
    isAdmin: boolean,
    _id: StringOrNum
}
```

****

## 2-18. Defining Object Types with Interfaces
與"type"關鍵字相似的"interface"關鍵字，"interface"關鍵字多數用在自定義物件型別，用法與"type"關鍵字稍有不同，因為"interface"關鍵字基本上用於物件，所以就不會有等號，用法如下所示。

"interface"關鍵字後接上自定義名稱，名稱後方直接接上花括號物件，物件裡面放key value pair來定義物件裡頭的型別。

```typescript!
interface Credentials{
    password: string,
    email: string
}
```

這個時候想要定義一個變數"creds"的型別，就可以拿上方自定義的Credentials型別來用。

```typescript!
let creds: Credentials;
creds = {
    password: "abc",
    email: "abc@gmail.com"
}
```

****

## 2-19. Interfaces vs Custom Types
因為內容偏多，所以拉出來另成一篇文章：[【TypeScript】extends vs. implements / type vs. interface](https://hackmd.io/@noz915/HJ4Rju_ja)

****

## 2-21. Being Specific With Literal Types
### 情境
現在我們希望role變數不只被限制為string，我們還希望role變數被限制只能為三種string："admin"、"user"、"editor"。

### 解決辦法
利用TS的Literal Types。

### 實作範例
```typescript!
let role: "admin" | "user" | "editor";
```
試試看assign以上字串給role變數都不會報錯：
```typescript!
role = "admin";
role = "user";
role = "editor"
```
不過如果我們assign以上三個字串以外的字串呢？就會報錯了！
![image](https://hackmd.io/_uploads/Hy4vsF_ja.png)

****

## 2-22. Adding Type Guards
綜合上一節的內容，看看可以怎麼運用，以下是簡單的範例：
```typescript!
type Role = "admin" | "user" | "editor"

function performAction(action: string | number, role: Role){
    if(role === "admin" && typeof action === "string"){
        // do something here ...
    }
}
```

****

## 2-23. Type Guards & Type Narrowing - A Closer Look

以下是老師整理的內容。

Type Guards & Type Narrowing - A Closer Look
When using "Type Guards" (i.e., if statements that check which concrete type is being used), TypeScript performs so-called "Type Narrowing".

This means that TypeScript is able to narrow a broader type down to a more specific type.

Consider this example:

```typescript!
function combine(a: number | string, b: number | string) {
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
  }
 
  return `${a} ${b}`;
}
```

In this example, inside the if statement, TypeScript narrows the types of a & b from "either a number or a string" down to "definitely a number" - because that's what the condition of the if check says (and TypeScript "understands" that).

After the if statement, TypeScript understands that a and b are again either a number or a string"  because we only make it into the if statement if both are of type number. The type therefore is broader again.

You can add all kinds of "Type Guards" to run code conditionally and get TypeScript to narrow a type:

* Use JavaScript's typeof operator as shown above to check if you're dealing with a string, number, boolean, object, function, symbol or bigint type

* Use JavaScript's instanceof operator to check if an object value is based on some specific class

* Use JavaScript's in operator to check if an object contains a specific property

**Important:** You can NOT check if a value meets the definition of a custom type (type alias) or interface type. These are TypeScript-specific features for which no JavaScript equivalent exists. Therefore, since those if checks need to run at runtime, you can't write any code that would be able to check for those types at runtime.

For example, the below code won't work because the User type does not exist once the code is compiled to JavaScript:

```typescript!
type User = {
  name: string;
  age: number;
};
 
type Admin = {
  name: string;
  age: number;
  permissions: string[];
};
 
function login(u: User | Admin) {
  if (typeof u === User) {
    // do something
  }
}
```

But you could check for the existence of the permissions property since only the Admin object will have one:

```typescript!
function login(u: User | Admin) {
  if ('permissions' in u) {
    // do something
  }
}
```

That code would work at runtime.

****

## 2-24. Making Sense Of Generic Types
> 參考資料：[[TypeScript] Generics 的使用情境](https://medium.com/hannah-lin/typescript-generics-%E7%9A%84%E4%BD%BF%E7%94%A8%E6%83%85%E5%A2%83-f5d714ff1fcf)

Generic Types看起來是暫時不確定會用什麼型別，所以利用`<T>`先替代，之後要使用這個自定義型別時，再定義即可，這邊還沒深入研究，先放上老師上課的範例。

* 範例一
    ```typescript!
    type DataStorage<T> = {
        storage: T[];
        add: (data: T) => void;
    }

    const textStorage: DataStorage<string> = {
        storage: [],
        add(data){
            this.storage.push(data);
        }
    }
    ```
    ![image](https://hackmd.io/_uploads/S1Q2mcusT.png)

* 範例二
    結合前面章節的User class。
    ```typescript!
    interface Credentials{
        email: string,
        password: string
    }
    class User implements Credentials{
        email: string;
        password: string;
        nickname: string
    }
    //定義generic type
    type DataStorage<T> = {
        storage: T[];
        add: (data: T) => void;
    }

    //使用generic type
    const userStorage: DataStorage<User> = {
        storage: [{
            email: "abc@gmail.com",
            password: "abc",
            nickname: "Amyyy"
        }],
        add(data){
            this.storage.push(data);
        }
    }
    ```
    ![image](https://hackmd.io/_uploads/BkZGS9Osa.png)

* 範例三
    generic type運用於function中。
    ```typescript!
    function merge<T, U>(a: T, b: U){
        return{
            ...a,
            ...b
        }
    }

    const newUser = merge<{name: string}, {age: number}>({name: "Max"}, {age: 20})

    //也可以省略<{name: string}, {age: number}>
    //TS會自動理解T, U目前被誰替代
    const newUser = merge({name: "Max"}, {age: 20})
    ```
    ![image](https://hackmd.io/_uploads/HkpTP9ujT.png)
