---
# System prepended metadata

title: 讀書會 - 02 - TypeScript
tags: [讀書會]

---

<style>
.tilte-bg {
  background: #C25F3C;
  color:#FFF;
  padding:2px 8px 5px 8px;
  border-radius:5px;
  font-size:13px;
  display:inline-block;
}

.warning{
  color:#A60101;
}

.tip {
color:#A60101;
}
p{
  font-size:15px;
}
</style>

# 讀書會 - 02 - TypeScript
###### tags: `讀書會`
> 2021/02/13 筆記

### 關於型別

<p>「型別」是一組值並可以使用它們來執行特定的行為。</p>

#### **1.JavaScript 中有 7 種原始型別 :**

* Number
* String
* Boolean
* Null
* Undefined
* Symbol
* Object（包含 Function 和 Array）

#### **2.TypeScript 基礎型別 :**

* Array
* Any
* Enum
* Void
* Never
* 另外包含 JavaScript 中有 7 種原始型別

---
 
### 關於TypeScript 基礎型別
<h5 class="tilte-bg">any</h5>

<p>允許賦值為任意類型 ( string . Array . Boolean...皆可 )，any 允許可以訪問任何操作、任意屬性(運算、函數)，由於開發時期 any 型別 TypeScript 認為它可以是任何型別。所以調用不存在的方法 TS 也不會爆錯可以可以正常編譯，但 runtime 就會出錯(範例1)。<br/>
any 返回的内容的類型都是 any 型別，即便為非相同型別相加也不會回報錯誤(範例2)。<br/></p>
<p class="warning">※ 應盡可能避免使用它</p>

```
範例1:

let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();
```


```
範例2:

let a: any = 4; //any
let b: any = ['apple']; //any
let c = a + b //any
```


<h5 class="tilte-bg">unknown</h5>

<p>可以指派任何型別的值給 unknown ，但要對 unknown 型別操作時，必須透過細分()轉成 unknown 以外的型別，否則便會出錯。<br/></p>
<p class="warning">※ 應盡可能避免使用它</p>


<h5 class="tilte-bg">number</h5>

<p>number 在 TypeScript 包含了整數、浮點數、正數、負數以及 Infinity 或 NaN 都包含在裡面。處理很大的數字時可以使用數值分隔符號(底線)區隔，可以讓可讀性提高</p>

```
範例1:

let oneMillion = 1_000_000 // 等同 1000000

```

<h5 class="tilte-bg">string</h5>

<p>所有字串的集合。</p>

<h5 class="tilte-bg">symbol</h5>

<p>「型別」是一組值並可以使用它們來執行特定的行為。</p>

<h5 class="tilte-bg">Enums</h5>

<p>枚舉類型默認從0開始，如果想改從1開始將物件第一個key指向1範例如下。</p>


```
enum Status {
  SUCCESS = 1,     // 1
  PARAMETERERROR,  // 2
  ERROR,           // 3
}

反查
console.log(`Status: ${Status[1]}, Number: ${Status.SUCCESS}`)

// Status: SUCCESS, Number: 1

----------------------------------------------------------------------------------------

enum Status {
  SUCCESS,         // 0
  PARAMETERERROR,  // 1
  ERROR,           // 2
}

function getServe(status:any){
  switch (status) {
   case Status.SUCCESS:
    return 'status : Success';

   case Status.PARAMETERERROR:
    return 'status : Parameter Error';

   case Status.ERROR:
    return 'status : Error';
 
  } 
}

const result = getServe(1)
console.log(result)
// status : Parameter Error
```

<h5 class="tilte-bg">元祖</h5>

<p>元祖是Array的子型別可以強制規定Array中每個所引上的值為乙支特定的型別。不同於其他型別，元祖宣告時必須明確的定型而 Typescript 會從[]中推稕列的型別</p>

```
const List :(string|number)[] = ['Dora',22,'Tom',28]

// Duplicate identifier 'List'

const ListSec :[string,number,string,number] = ['Dora',22,'Tom',28]

// OK
```


<h5 class="tilte-bg">null</h5>

<p>null 屬於特殊型別代表空值，null 型別唯一值為 null。</p>

<h5 class="tilte-bg">undefined</h5>

<p>undefined 屬於特殊型別代表值尚未被定義，undefined 型別唯一值為 undefined。</p>

<h5 class="tilte-bg">void</h5>

<p>void 屬於特殊型別，是沒有明確回傳任何東西的函式、沒有return述句的函式。</p>

<h5 class="tilte-bg">never</h5>

<p>never 屬於特殊型別，是永遠不會回傳任何東西的函式。</p>


---

靜態類型 Static Typing
定義類型後無法再修改，並繼承型別所有方法如下圖，靜態類型可以通過接口進行使用(範例1)

![](https://i.imgur.com/faf5bRF.png)


```
範例1:
interface Employee {
  name:string,
  age:number,
  department:string
}

let newbieA :Employee= {
  name:'Dora',
  age:18,
  department:'DNG'
}

console.log('newbieA :',newbieA)

// newbieA :",  {
  "name": "Dora",
  "age": 18,
  "department": "DNG"
} 
```

靜態類型分為基礎靜態類型 / 對象靜態類型
類型註解:宣告變數+ 符號 ":" + 類型
類型推斷:即便宣告時沒有類型註解 Typescript 也會推斷分析目前值的類型;但如果 Typescript 無法分析變量類型就必須一定要寫類型註解

```
let count:number = 100;

```

基礎靜態類型

<h5 class="tilte-bg">number</h5>

```
let count:number = 100;
```

<h5 class="tilte-bg">string</h5>

```
let name:string = 'Tom';
```

<h5 class="tilte-bg">nul</h5>

<h5 class="tilte-bg">undefinde</h5>

<h5 class="tilte-bg">boolean</h5>

<h5 class="tilte-bg">void</h5>

<h5 class="tilte-bg">symbol</h5>

對象靜態類型

<h5 class="tilte-bg">object類型</h5>

宣告物件Key對應value類型再賦值

```
const Employee {
  name:string,
  age:number,
  department:string
} = {
  name: "Dora",
  age: 18,
  department: "DNG"
} 

```
<h5 class="tilte-bg">array類型</h5>

```
const Employee :string [] = ['Tom','Dora','Kimy']
```


<h5 class="tilte-bg">class類型</h5>

```
class Person{}
const Man : Person = new Person()
```

<h5 class="tilte-bg">函數類型 </h5>


```
當函式返回值必須是 string
const getName : () => string = () => {
  return `Hello Anna`
}
```

---

### 複合型別 Union & Intersection

#### **1.Union 聯集:**
<p>使用 " | " 只要至少其中一個型別被判定滿足，不管其他型別有沒有完整補齊<p>

```
type UserOnlyInfo1 = {
  name:string,
  age:number
}

type UserOnlyInfo2 = {
  skill:string,
  workexperience:boolean
}


type UnionSet = UserOnlyInfo1 | UserOnlyInfo2 


let correctInfo1 : UnionSet = {
  name:'Dora',
  age: 28,
  skill:'CSS3',
  workexperience:true
}


let correctInfo2 : UnionSet = {
  // name:'Rinatea', --> 雖然少了 UserOnlyInfo1 中 name 屬性,但是符合 UserOnlyInfo2 所有條件
  age: 28,
  skill:'CSS3',
  workexperience:true
}

let wrongInfo1 : UnionSet = {
  name:'Jill',
  // age: 28, --> 少了 UserOnlyInfo1 中 age 屬性
  skill:'Java',
  // workexperience:true --> 少了 UserOnlyInfo2 中 workexperience 屬性
}

wrongInfo1 將出現以下錯誤訊息 Type '{ age: number; workexperience: true; }' is not assignable to type 'UnionSet'.

```

#### **2.Intersection 交集:**
<p>使用 " & " 將兩組屬性進行結合，範例如下<p>

```
type UserInfo1 = {
  name:string,
  age:number
}

type UserInfo2 = {
  skill:string,
  workexperience:boolean
}

type IntersectionSet = UserInfo1  & UserInfo2 


let correctInfo : IntersectionSet = {
  name:'Dora',
  age: 28,
  skill:'CSS3',
  workexperience:true
}


let wrongInfo1 : IntersectionSet = {
  // name:'Rinarea', --> 少了 name 屬性
  age: 28,
  skill:'CSS3',
  workexperience:true
}

let wrongInfo2 : IntersectionSet = {
  name:'Jill',
  age: 28,
  skill:'Java',
  // workexperience:true --> 少了 workexperience 屬性
}

```

---

### 函式
在 TypeScript 可定義函式參數之型別;如傳入型別不對的引數將提醒引數不可指定為參數定義型別(範例1)

```
範例1:

let add = ( a:number ,b:number ) => ( a + b )

console.log(add(10,30)) // 40
console.log(add(10,'a')) // Argument of type 'string' is not assignable to parameter of type 'number'.

```

建構函式可建立一個新的 Function 物件，參數必須要是有效的 JavaScript 識別符號規則的字串，或是使用英文逗號「,」分隔開的字串清單(範例1)。
<p class="warning">※ 由於使用建構函式參數和回傳不具型別較不安全建議避免使用。</p>

```
範例1:

let greet = new Function('name','return "Hello " + name')

console.log(greet('Tom')) // Hello Tom
console.log(greet(['2','6'])) // Hello 2,6
```

#### **1.選擇性參數:**

可以使用 "?" 將參數標示為選擇性是否為必要參數，擇性參數必須放在參數列的尾端(範例1)。

```
範例1:

function messageLog( message:string,useId?:number ){
  let time = new Date().toISOString()
  console.log(time , message , useId || 201401)
}

messageLog('User clicked on button',910501) 
// "2021-02-19T17:43:13.899Z",  "User clicked on button",  910501 

messageLog('User signed out') 
// "User signed out",  201401 (預設值)

------------------------------------------------------------------------------

範例2:
type Contest = {
  appId?:string
  useId?:number
}

function messageLog1( message:string,context:Contest={} ){
  let time = new Date().toISOString()
  console.log(time , message , context.appId )
}

messageLog1('User clicked on button',{ appId:'iphone X' })
// "2021-02-19T18:08:03.208Z",  "User clicked on button", iphone X
```
<p class="warning">※ 如果移除預設值 Typescript 可以從預設值推論參數的型別</p>


#### **2.其餘參數:**

當不確定數量的參數，並將其視為一個陣列

---

### 類別class、繼承extends 與介面Interface

<h5 class="tilte-bg">class</h5>

以 class 關鍵字宣告類別可以使用 extends 關鍵字來擴充方法。

<h5 class="tilte-bg">public </h5>

class內部 & class外部任何地方都可以存取(預設存取層)。

<h5 class="tilte-bg">protected</h5>

跟與 private 相同只能可從此 class內部使用及繼承父class的其子類別的實體存取。

```
class Game {
    protected content:string = "Game Instructions";
}

class MarioGame extends Game {
    log(){
        return this.content
    }
}

// OK
```


<h5 class="tilte-bg">private</h5>

儘可以從此類別的實體內部進行存取，也不允許繼承內部使用。

```
class Game {
    private content:string = "Game Instructions";
}

const MarioGame = new Game()
MarioGame.content
// 錯誤提示 content 為私有的 Property 'content' is private and only accessible within class 'Game'.
```
<h5 class="tilte-bg">readonly</h5>

代表初始值只能被讀無法寫入防止屬性被更改，和const差異 : 變數的話就用 const 、是屬性的話就用 readonly。

<h5 class="tilte-bg">abstract</h5>

意味無法直接實體化該類別，但依舊可以載此類別定義其他方法以及不同的業務邏輯

```
abstract class Employee {
    abstract skill(){}
}

class Engineer extends Employee{
    private description:string = 'Vue & JavaScript' 
    skill(){
        return `Skill is ${description}`
    }
}

class Designer extends Employee{
    private description:string = 'HTML & CSS'
    skill(){
        return `Skill is ${description}`
    }
}

```

<h5 class="tilte-bg">super</h5>

可直接存取父類別的方法

<h5 class="tilte-bg">constructor</h5>

如果使用繼承 extends 子類別繼承父類別後要使用 constructor 必須加上 super() 並傳遞父類層 constructor 所需要的值，即使父類別沒有使用 constructor 子類別也必須加上 super()

<h5 class="tilte-bg">Getter</h5>

關鍵字是 get , 如果變數是 private 外部想要存取使用可以用 get return

<h5 class="tilte-bg">Setter</h5>

關鍵字是 set 可以在賦予值時做加工

<h5 class="tilte-bg">static</h5>

ststic 靜態屬性不需要重新 new 對象就可以直接使用

#### **1.class:**

```
class Game {
    content = "Game Instructions";
    start(){
        return this.content
    }
}

const MarioGame = new Game()
MarioGame.start()
// Game Instructions
```


#### **2.extends:**

```
class RingFitGame(子) extends Game(父) {
    run(){
        return 'Run 10 kilometers'
    }
}

const PokemonGame = new RingFit()

PokemonGame.start()
// Game Instructions

PokemonGame.run()
// Run 10 kilometers
```

#### **3.interface:**
一個介面可以擴充任何形狀，物件 / type / class / interface 都可以，
在同一個 scope 名稱相同的多個介面會自動被合併，同名的多個型別再編譯時發現錯誤
介面可以宣告實體特性，但他們不可以宣告可見的修飾詞 (public、protected、private)也不可以實立化(不能 new )
並且不可以使用 static 關鍵字
```
interface Food {
    calories:number
    tasty:boolean
}

or

type Food = {
    calories:number
    tasty:boolean
}
```

#### **4.constructor:**

```
class Game {
    public gameName:string;
    constructor(gameName:string){
    this.gameName = gameName
    }
}

or 

class Game {
    constructor(public gameName:string){}
}

const RingFitGame = new Game('RingFitGame')

console.log(RingFitGame)
//Game: {"gameName": "RingFitGame"} 


class PokemonGame extends Game {
    constructor(public gameTime:number){
        super('PokemonGame')
    }
}

const PokemonGame2 = new PokemonGame(100)

console.log(PokemonGame2.gameName)
// PokemonGame
console.log(PokemonGame2.gameTime)
// 100
```

#### **5.Getter &　Setter:**
```
class User {
    constructor(private _age:number){}
    get userAge(){
        return this._age + 5
    }
    set userAge(age:number){
    this._age = age - 9 
    }
}

const UserA = new User(20)
UserA.userAge = 30
console.log(UserA.userAge)

// 26 ---> 經過封裝 30 + 5 - 9

```

#### **6.static:**
```
class User {
    static getUserAge(age:number){
    return `今年${age}歲`
    }
}

console.log(User.getUserAge(18))

// 今年18歲

```

---

### 子型別 & 超型別

![](https://i.imgur.com/pGC1crt.jpg)

---
### 泛型

泛型的目的是在調用 function、泛型類、函數参數、函數返回值時必須處理各種數據類型時的約束。並利用"<>"中加上一個類型變量T(傳遞參數的型別)。

T代表類型，在定義泛型時通常用作第一個類型變量名。而T其實可以用任何有效的名稱替換並不限於一個類型變量()。
```
function identities<T, U>(arg1: T, arg2: U): T {
return arg1;
}
```

泛型可以使用於function, Class. Interface
```
class Person<T>{} 面
function Person<T>(arg: T): T {return arg;}
interface Person<T> {}  
```

情境1:參數可以是 string | number (範例1)

```
範例1:

function join(first: string|number ,second: string|number){
return `first:${first} second:${second}`
}

console.log(join<string>('dora','test'))
// "first:dora second:test" 


console.log(join<string>('dora',1))
// "first:dora second:1" 

or

function join<T,P>(first:T ,second: P):string|number{
return `first:${first} second:${second}`
}

console.log(join<string,number>('dora',1))
// "first:dora second:1"

```

情境2:參數限制型別需一致(範例2)

```
範例2:

function join<name>(first: name ,second: name){
return `first:${first} second:${second}`
}

console.log(join<string>('dora','test'))
// "first:dora second:test" 


console.log(join<string>('dora',1))

Argument of type 'number' is not assignable to parameter of type 'string'
```

情境3:泛型中參數為數組(範例3)

```
範例3:

function join<T>(parmas:T[]):T{
return parmas
}

or

function join<T>(parmas:Array<T>):T{
return parmas
}

console.log(join<string>(['dora','test555']))
// ["dora", "test555"] 
```
泛型接口
```
interface Identities<T, U> {
    id1: T;
    id2: U;
}

function identities<T, U> (arg1: T, arg2: U): Identities<T, U> {
   console.log(arg1 + ": " + typeof (arg1));
   console.log(arg2 + ": " + typeof (arg2));
   let identities: Identities<T, U> = {
    id1: arg1,
    id2: arg2
  };
  return identities;
}
```

類別泛型

```
class List{
    private data:any = [];
    push(element: any){
        this.data.push(element)
    }
    pop(){
        return this.data.pop()
    }
}
let addItem = new List();
addItem.push(5)
console.log(addItem)
// List: { "data": [5]} 
```

---
### 進階型別

#### **1.條件式型別:**
宣告依存 U 和 V 的型別 T 如果 U <: V 型別 T 就將指定給 A 否則則指定給 B， 
IsString 接受泛型 T ，條件是 T extends string (T 是 string 的子型別嗎?)
```
type IsString<T> = T extends string ? true : false
```

#### **2.分配式條件式型別:**
宣告依存 U 和 V 的型別 T 如果 U <: V 型別 T 就將指定給 A 否則則指定給 B， 
IsString 接受泛型 T ，條件是 T extends string (T 是 string 的子型別嗎?)
```
type ToArray<T> = T[]
type A = ToArray <number>= number[]
type B = ToArray <number | string>= (string|number)[]
```

#### **3.infer:**
如果 T 能賦值給(param: infer P) => any，則結果是(param: infer P) => any類型中的參數P，否則返回為T
```
type ParamType<T> = T extends (param: infer P) => any ? P : T;

interface User {
  name: string;
  age: number;
}

type Func = (user: User) => void;

type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
```


TypeScript 型別系統

型別推論(Type Inference)
沒有明確註記資料型別，TS 編譯器便會自動推論出資料型別


型別斷言(Type Assertion)
TS 允許開發者覆蓋它的推論，這樣的機制稱為「型別斷言」。編譯器會接受開發者手動寫下的斷言，並且不會再送出警告錯誤。

型別斷言有兩種寫法：

第一種是<型別>值 (angle-bracket <>)寫法
```
let code: any = 123; 
let employeeCode = <number> code; 
```

第二種則是值 as 型別 (as keyword)寫法
```
let code: any = 123; 
let employeeCode = code as number;
```

型別註解(Type Annotation)
透過手動註解的方式，明確宣告資料型別，告訴編譯器必須符合註解的型別，以方便在開發時就抓到變數的錯誤賦值問題。在做型別註解時，會在變數、參數或屬性後面加上冒號:型別，冒號後方可加一個空格，例如

```
const age: number = 32
```


型別註解告訴編譯器這個資料必須永遠都是這個型別 ; 而型別斷言則主要用在覆蓋 TS 編譯器自動進行的型別推斷和型別相容性規則(Type Compatibility)，告訴編譯器你知道這個值要符合斷言的型別，從而避免了上面範例中型別不兼容的錯誤產生。

TypeScript 型別推論
TypeScript 會在沒有明確的指定型別的時候推測出一個型別，這就是型別推論。
如果定義的時候沒有賦值，不管之後有沒有賦值，都會被推斷成 any 型別而完全不被型別檢查：