<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)

```
範例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歲
```
---
### 子型別 & 超型別

---
### 泛型
泛型的目的是在調用 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 型別而完全不被型別檢查: