---
# System prepended metadata

title: Typescript tutorial
tags: [Typescript]

---

###### tags: `Typescript`

# Typescript tutorial
[youtube教學](https://www.youtube.com/watch?v=2pZmKW9-I_k&list=PL4cUxeGkcC9gUgr39Q_yD6v-bSyMwKPUI)

## Compiling TypeScript
- `tsc filename.ts`: 將ts檔 Compiling to TypeScript, 每次修改完code都要執行一次才會重新 Compiling
- `tsc src/filename.ts`: 如果檔案不在目前terminal所在路徑, 可以再加src路徑, 作用同上
- `tsc filename.ts -w`: 修改完code 會即時將ts檔 Compiling to TypeScript, 可console看即時更新結果


## Type Basics 2~6
```typescript=
//string, number, boolean
let name:string;
let age: number;
let isOpen: boolean;

//array
let arr1: string[] = [];  //array內value只能是string
let arr2: number[] = [];  //array內value只能是number
let arr3: any[] = [];    //array內value可以是任意型別

//union types
let mixed: (string|number|boolean)[] = []

let uuid: string|number;
uuid = 'emma';
uuid = 123;

//object
//任意物件內容都可以
let obj: object;
obj = {name:'emma', age:123};

//限定物件key與value型別
let objDetail:{
    name:string,
    age: number,
    isDrive: boolean,
};

objDetail = {
    name: 'emma',
    age: 123,
    isDrive: false,
};

// 物件內value型別可以任意
let obj = {name: any, age: any};

obj = {
    name: 'emma',
    age: 123
};

// 任意型別
let age: any = 25;
age = true;
```

## Better Workflow & tsconfig 7, Modules 14
- terminal cd 到 src file
- `tsc --init`在src資料夾會出現 tscconfig.json
```javascript=
// tscconfig.json
{
    "compilerOption": {
        "target": "es6", //指定編譯生成的JS版本
        "module": "commonjs", //指定生成哪種模組 ex:es2015
        "rootDir": "./src" //.ts檔案所在位置
        "outDir": "./public": // .ts要compiler放到哪裡
        "forceConsistentCasingInFileNames": true, //強制區分大小寫, 默認為false
        "files": ["hello.ts"], //屬性指定要編譯的 TS 檔案
        "include": ["src"]或["src/**/*.ts"], //屬性設定編譯時包含哪些檔案或資料夾, 路徑可調整
        "exclude": ["node_modules"], //屬性設定編譯時排除哪些檔案或資料夾
    },
    
}

// *-表示匹配0至多個字元(不包含分隔符號)
// ?-匹配一個相符字元（不包含分隔符號）
// **/-表示匹配所有子資料夾

//ps:以下code可以過, 但如果forceConsistentCasingInFileNames為true時, 
    會報錯誤：'2a' !== '2A'

//2a.ts
export const PI = 3.1415926;
//1a.ts
import PI from './2A.ts';
function fun(){
  return PI;
}
```
- `tsc -w` 在vscode的filename.ts檔案, 下這個指令`tsc -w` 開啟同步compiler, 當改code的時候, 就會同步輸出js檔, 只要一更改ts就會馬上改js
[tscconfig.json設定參考](https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/TypeScript%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6tsconfig%E7%AE%80%E6%9E%90.md)

## Function Basics 8
```typescript=
//宣告型別為 Function 
let greet: Function;
greet = () => {
    console.log('hello world');
}

//宣告function的參數型別
//有參數c時宣告型別, 並且有 defaultValue 10
const add = (a:number, b:number, c:number | string = 10) => {
    console.log(a+b);
    console.log(c); //如果沒有= defaultValue 10, log結果為 undefind
}
add(5,10, 'emma');

//有參數c時宣告型別, 且c不能=預設 defaultValue 10
const add = (a:number, b:number, c?:number | string) => {
    console.log(a+b);
    console.log(c); //如果沒有= defaultValue 10, log結果為 undefind
}
add(5,10, 'emma');

//定義function沒有回傳value: void
const plusFn = (a:number, b: number, c?:number):void =>{
    console.log(a+b);
    console.log(c);
}

const plusFn = (a:number, b: number, c?:number):number =>{
    return a+b+c;
}
```

## Type Aliases 9
```typescript=
//用type宣告型別
type StringOrNum = string|number;
type ObjWithName = {name:string, uid:StringOrNum};

const loginFn = (uid:StringOrNum, name:string) => {
    console.log(`${name} has a uid is ${uid}`);
}

const helloFn = (user:ObjWithName) => {
    console.log(`${user.name} say hello!`);
}
```

## Function Signatures 10, 宣告function三種例子, 參數與回傳值型別的差異
- 宣告function型別：
```typescript=
let greet: Function;

//example1: 宣告greet變數有兩個參數, 並定義型別, 與回傳值型別
let greet: (a:string, b:string) => void;

greet = (name:string, greeting:string):void => {
    console.log(`${name} says ${greeting}`);
}

// example2: 如果有宣告回傳值則邏輯結束一定要回傳符合型別的value
let cal: (a:number, b:number, action:string) => number; //回傳number

cal= (a:number, b:number, action:string) => {
    //if 沒有else會報錯, 一定要回傳number
    if(action === 'add') {
        return a+b
    }else {
        return a-b
    }
}

// example3: 參數是物件
let loginFn: (obj: {name: string, age:number}) => void;

type UserType = {name: string, age:number};

loginFn = (user:UserType) => {
    console.log(`${user.name} is ${user.age} years old.`);
}
```

## The DOM & Type Casting 11: 宣告DOM的型別有哪些
```typescript=

//example1: HTMLFormElement or HTMLAllCollection....等會有提示, 如下：
//<form> id="type"
const form = document.querySelect('.main') as HTMLFormElement;
console.log(form.children);

//example2: 
//<select> id="type"
const type = document.querySelect('#type') as HTMLSelectElement;

//<input> id="type"
const type = document.querySelect('#type') as HTMLInputElement;

//<form> id="type"
const type = document.querySelect('#type') as HTMLFormElement;

```

## Classes 12, Public, Private & Readonly 13
```typescript=
class Invoice {
    readonly client:string; //不管class內外都只能讀, 不能重新assign value
    private detail:string; //只能在class裡面access和賦值
    public amount:number; //class外也可以access和賦值
    
    constructor(c: string, d: string, a:number){
        this.client = c;
        this.detail = d;
        this.amount = a;
    }
    
    //另一種寫法
    constructor(
        readonly client:string;
        private detail:string;
        public amount:number;
    ){}
    
    format(){
        // this.client = 'something else' // error, 因為client是readonly不能重新賦值
        return `${this.client} owes ${this.detail} for ${this.amount}`
    }
}

const invOne = new Invoice('mario', 'work on the mario website', 250);
const invTwo = new Invoice('luigi', 'work on the mario website', 300);

//class就是一種型別
let invoices: Invoice[] = []; //class定義型別
invoices.push(invOne);
invoices.push(invTwo);

invoices.forEach(inv =>{
    //因為inv.detail是private所以不能在class外存取, 但可從inv.format()取到detail的值, 因為format()是在class裡面access detail
    console.log(inv.client, inv.detail, inv.amount, inv.format());
})

```

##  Interfaces 15
```typescript=
//interface定義型別, 名稱要大寫開頭
interface User {
    name: string;
    age: number;
    speak(a: string): void;
    spend(b: number): number;
}

// 宣告userA的型別為User
const userA: User = {
    name: 'emma',
    age: 123,
    speak(text: string):void {
        console.log(text);
    },
    spend(amount: number): number {
        console.log(amount);
        return amount;
    }
}

//用Interfaces宣告function argument 型別範例
const userFn = (users:User) => {
    console.log('hello', person.name);
}

userFn(userA); //執行userFn
```

##  Interfaces with Classes 16: class如何使用Interfaces
```typescript=
// src/interfaces/HasFormatter.ts
// 定義format回傳字串
export interfaces HasFormatter {
    format(): string;
}

// src/classes/Invoice.ts
import { HasFormatter } from '../interfaces/HasFormatter.ts';

export class Invoice implements HasFormatter {
    constructor(
        readonly client:string; //不管class內外都只能讀, 不能重新assign value
        private detail:string; //只能在class裡面access和賦值
        public amount:number; //class外也可以access和賦值
    ){}
    
    format(){
        // this.client = 'something else' // error, 因為client是readonly不能重新賦值
        return `${this.client} owes ${this.detail} for ${this.amount}`
    }
}
```

##  Rendering an HTML Template 17
- 參照react寫法即可


##  Generics(泛型) 18
- Generics（泛型）很重要的一點，就是讓我們寫的方法可以適用在不同的型別，而非只能使用在單一型別。
- 定義泛型
```typescript=
// type
type Dict<T> = {
  value: T;
};

// interface
interface WrappedValue<T> {
  value: T;
}

// arrow function
const identity = <T>(x: T): T => x;

// function
function identity<T>(x: T): T {
  return x;
}
```
- 範例如下：可以dynamic defind data type也可以重複使用uid, name, errorCode
```typescript=
interface Resource<T> {
    data: T; //can dynamic defind
    uid: number; //repeact use
    name: string; //repeact use
    errorCode: number; //repeact use
}

const docObj:Resource<object> = {
    data: {name: 'emma'};
    uid: number;
    name: string;
    errorCode: number;
}

const docStrArr: Resource<string[]> ={
    data: ['emma', 'mark', 'lisa'];
    uid: number;
    name: string;
    errorCode: number;
}
```
![](https://i.imgur.com/CRTqzXA.png)
[Generic範例: type可以重複使用就看傳入的type是什麼1](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.solution.1.ts)
[Generic範例: type可以重複使用就看傳入的type是什麼2](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.problem.ts)


## 帶有限制的泛型 Type Parameter with Constraints:透過 `extends` 的使用，可以建立帶有**「限制」**的泛型：
```typescript=
interface WrappedValue<T extends string> {
  value: T;
}

// ⭕️ T 滿足 string 的型別
const val: WrappedValue<'Aaron' | 'PJ'> = {
  value: 'Aaron',
};

// ❌ T 不滿足 string 時會噴錯
// Type 'number' does not satisfy the constraint 'string'.
const val: WrappedValue<number> = {
  value: 30,
};

// ❌ 因為沒有給 T 預設值，所以不能留空
// Generic type 'WrappedValue<T>' requires 1 type argument(s).
const val: WrappedValue = {
  value: 30,
};
```
- 實際的例子像這樣：
```typescript=
// 接收 array: T[] 做為參數，回傳 Dictionary {[k: string]: T}
// 若把 T 的限制 { id: number } 拿掉的話，將會沒辦法確定 T 是帶有 id 的物件
const arrayToDict = <T extends { id: number }>(array: T[]): { [k: string]: T } => {
  const out: { [k: string]: T } = {};
  array.forEach((val) => {
    out[val.id] = val;
  });
  return out;
};
```


## 帶有預設值的泛型 Generic parameter defaults
- 可以給泛型預設值，舉例來說，下面的程式碼指的是：沒有給 T 的話，T 預設的型別會是 string
```typescript=
interface WrappedValue<T = string> {
  value: T;
}
```
- 但如果只是定義預設值的話，使用者可以任意更改該型別，例如：
```typescript=
// 預設 WrappedValue 中使用的型別是 string，但使用者可以改成自己想要使用的
const val: WrappedValue<number> = {
  value: 30,
};
```
- 這時候，可以透過 extends 限制使用者給定的泛型，例如：

    - 如果沒有提供 T 則預設該型別是 string
    - 如果有給 T
        - T 需要滿足 string，否則會噴錯
        - 當 T 滿足 string 時，T 會變成變成實際代表的型別
```javascript=
interface WrappedValue<T extends string = string> {
  value: T;
}

// 如果沒有提供 `T` 則預設該型別是 `string`
const val: WrappedValue = {
  value: 'hello',
};

// ❌ 如果有給 T，但 T 不滿足 string 時，會噴錯
// Type 'number' does not satisfy the constraint 'string'.
const val: WrappedValue<number> = {
  value: 30,
};

// ⭕️ 如果有給 T，當 T 滿足 string 時，T 會變成變成實際代表的型別
const val: WrappedValue<'Aaron' | 'PJ'> = {
  value: 'Aaron',
};
```


[pjchender: generic進階用法](https://pjchender.dev/typescript/ts-generics/)


## Enums 19
- 基本跟在function component外宣告const物件類似
- 差異是 enums 是常數希望不能被更改, 提高程式可讀性
- 範例: value可以是數字or字串
```typescript=
enum requestStatusCodes {
  error = 400,
  success = 200,
}

enum requestWrongCodes {
  missingParameter = 'A',
  wrongParameterType = 'B',
  invalidToken = 'C',
}
```

[TypeScript | 善用 Enum 提高程式的可讀性 - 基本用法 feat. JavaScript](https://medium.com/enjoy-life-enjoy-coding/typescript-%E5%96%84%E7%94%A8-enum-%E6%8F%90%E9%AB%98%E7%A8%8B%E5%BC%8F%E7%9A%84%E5%8F%AF%E8%AE%80%E6%80%A7-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-feat-javascript-b20d6bbbfe00)

## Tuples 20
```typescript=
let values: [string, string, number]; // defind type
values = [obj.name, obj.address, obj.age]; //declare value
newValues = [...]; // 解壓縮前要defind type才不會報錯
```

## 斷言 as unknown
- 通常api拿回來的資料ts無法事先知道, 因此可以用as斷言人工定義會來的資料會是什麼類型
```javascript=
type Data ={
    userId: number,
    id: number,
    title: string,
    completed: boolean,
}

async function getData() {
    const res = await fetch('https://jsonplaceholder.typicode.com/todo/1');
    const data = await res.json() as Data;
}
```

## Record<key, value>
- 有時候只知道key跟value的型別
```javascript=
interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};
```

## Pick<Type, Keys>
- 只需要某個type裡面某幾個項目, **篩選出某幾項**
```javascript=
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};
```

## Omit<Type, Keys>
- 省略掉某個type的某幾項, **省略掉某幾項**
```javascript=
interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};

type TodoInfo = Omit<Todo, "completed" | "createdAt">;
 
const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};
```

## react component參數寫法
```javascript=
type UsersDataType = {
  data: {
    total: number;
    users: UserType[];
    factories: GroupType[];
    departments: GroupType[];
    groups: GroupType[];
  };
}

type PropType = {
  data: UsersDataType;
  updateTable: Function;
};

//用在function component的參數方法
const App: React.FC<PropType> = ({data, updateTable}) => {
    return<div>首頁</div>
}
```

## Wrap Up 21略

