TypeScrip 基礎與進階概念_Part 1

型別推論 ( Type Inference )

型別推論 就是在没有明確指定型別的情況下,由 TS 自動判斷型別

型別斷言 ( Type Assertion )

型別斷言 則是手動指定資料型別,可以覆蓋掉 TS 自動推論的資料型別

語法

型別斷言有兩種寫法:

  1. <型別>值 ( angle-bracket <> )寫法
1
2
let code: any = 123; 
let employeeCode = <number> code;
  1. 值 as 型別 ( as keyword )寫法
1
2
let code: any = 123; 
let employeeCode = code as number;
  1. 非空斷言運算符(Non-null Assertion Operator)
1
2
3
4
5
6
7
8
9
10
function getElementText(id: string): string {
// 假設這個函數總是會找到一個 DOM 元素
const element = document.getElementById(id);

// 使用非空斷言運算符,因為我們相信這個元素絕對存在
return element!.textContent!;
}

// 假設這裡確保 DOM 中有一個 id 為 "my-element" 的元素
console.log(getElementText("my-element"));

當你確定一個變數或屬性在某一時刻不會是 null 或 undefined 時,可以使用 ! 來告訴 TypeScript 編譯器你有這個保證,從而避免類型檢查錯誤。

使用情境

  • 情境1. JS 程式碼轉換成 TS 程式碼
    1
    2
    3
    4
    // Javajcript
    const foo = {}
    foo.bar = 123 // Error: property 'bar' does not exist on `{}`
    foo.bas = 'hello' // Error: property 'bas' does not exist on `{}`
    我們將上方的 JS 程式碼放進 TS 中會出現錯誤,因為 foo 的型別推論是沒有任何屬性的物件,因此,不能在屬性上添加 bar 或 bas ,這時候透過型別斷言可以避免這個問題
1
2
3
4
5
6
7
8
9
10
11
12
// TypeScript
interface Foo {
bar: number;
bas: string;
}

const foo = {} as Foo;
或是也可以寫成
const foo = <Foo> {}

foo.bar = 123; //OK
foo.bas = 'hello'; //OK
  • 情境2. 將聯合型別的變數指定為更加具體的型別

聯合型別,表示值可以是多種指派型別的其中一種。

下方的程式碼表示 getLength 這個函式的參數可能為字串或數字的其中一種,然而 length 不是字串和數字的的共同属性,會報錯。

1
2
3
4
5
6
7
8
9
10
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}

// error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

這時候就可以使用「型別斷言」將 something 斷言為字串(string)

1
2
3
4
5
6
7
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}

TS 的型別斷言並非型別轉換(Type casting)
型別斷言有點類似其他強語言如 Java 中的型別轉換,但實際上仍是不同的。斷言聯合型別中不存在的型別是不行的。

1
2
3
4
5
6
function toBoolean(something: string | number): boolean {
return <boolean>something;
}

// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
// Type 'number' is not comparable to type 'boolean'.

型別註解 (Type Annotation)

型別註解則是明確指定資料型別

語法

變數、參數或屬性:型別

1
2
3
4
5
6
7
8
9
10
11
12
//變數的型別註解
const age: number = 32

//函式參數的型別註解
function display(name: string){
console.log(name);
}

//函式參數/回傳值的型別註解
function display(a: number,b: number): number{
return a + b
}

補充:型別註解 v.s 型別斷言的比較

  1. 大部分情況下會使用型別註解 ; 型別斷言使用情境較少。
  2. 型別註解告訴編譯器這個資料必須永遠都是這個型別 ; 而型別斷言則主要用在覆蓋 TS 編譯器自動進行的型別推斷和型別相容性規則(Type Compatibility),告訴編譯器你知道這個值要符合斷言的型別,從而避免了上面範例中型別不兼容的錯誤產生。關於兼容性的討論 =>stackoverflow
  3. 型別註解大部分使用在初始化階段,像是宣告變數、函式參數或回傳值型別等 ; 而型別斷言可能用在接收外部參數,中間過程需要明確指定資料型別的時候。

基本型別 (Basic Types)

  • 原始型別( Primitive Types)
    1. number (數字)
    2. string (字串)
    3. boolean (布林值)
    4. undefined
    5. null
    6. symbol
    7. BigInt

BigInt (TypeScript 3.2 之後支援)

  • 物件型別 Object Types

    1. Function (函式)
    2. Array (陣列)
    3. Object (物件)
    4. Class (類別)
    5. Class (類別) new 出的物件實例 (instance)
  • void (空值):

    1. 表示不回傳任何值(等於預設回傳 undefined)
    2. 只能將它賦值為 undefined 和 null
  • Null 和 Undefined

與 void 的區別是,undefined 和 null 是所有型別的子型別

也就是說 undefined 型別的變數,可以賦值給 number 型別的變數:

1
2
// 這樣不會報錯
let num: number = undefined;

而 void 型別的變數不能賦值給 number 型別的變數:

1
2
3
4
let u: void;
let num: number = u;

// Type 'void' is not assignable to type 'number'.

Any (任意值)

宣告一個變數為任意值之後,對它的任何操作,返回的內容的型別都是任意值。

在任意值上訪問任何屬性都是允許的:

1
2
3
  let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

也允許呼叫任何方法:

1
2
3
4
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

未宣告型別的變數

變數如果在宣告的時候,未指定其型別,那麼它會被識別為任意值型別:

1
2
3
4
5
let something: any;
something = 'seven';
something = 7;

something.setName('Tom');

聯合型別(Union Types)

聯合型別使用 | 分隔每個型別,表示取值可以為多種型別中的一種。

當 TypeScript 不確定一個聯合型別的變數到底是哪個型別的時候,我們 只能存取此聯合型別的所有型別裡共有的屬性或方法

1
2
3
4
5
6
    function getLength(something: string | number): number {
return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

上例中,length 不是 string 和 number 的共有屬性,所以會報錯。

存取 string 和 number 的共同屬性是沒問題的:

1
2
3
function getString(something: string | number): string {
return something.toString();
}

介面(Interfaces)

在 TypeScript 中,我們使用介面(Interfaces)來定義物件的型別。

TypeScript 中的介面是一個非常靈活的概念 :

  1. 用於對 類別的一部分行為 進行抽象
  2. 用於對 物件的形狀(Shape) 進行描述

在物件導向程式語言中,介面(Interfaces)是一個很重要的概念,它是對行為的一種抽象,而具體如何行動則需要由類別(classes)去實現(implement)。

賦值的時候,變數的形狀必須和介面的形狀保持一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 正確示範
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom',
age: 25
};

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

// 錯誤示範 1 - 定義的變數比介面少了一些屬性是不允許的
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom'
};

// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.

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

// 錯誤示範 2 -多一些屬性也是不允許的
interface Person {
name: string;
age: number;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.