TypeScript 複習進階三部曲 (1) – 把 TypeScript 當強類型語言使用

前言

原本是想照着 TypeScript 官網 handbook 寫個教程的. 但提不起那個勁...

所以呢, 還是用我自己的方式寫個複習和進階筆記就好了唄.

 

學習 TypeScript 的三個階段

我覺得學 TypeScript 有三個階段

第一個階段是把 TypeScript 當 C# / Java 這些靜態類型語言來寫.

你會用到 Class, Interface, Generic, Enum 這些東西. 但其實這些只是 TypeScript 很小的部分而已.

第二階段是邏輯類型

C# 是沒有辦法表達出類型間的邏輯關係的. 你不能表達 "這個變量的類型就是那個函數的第一個參數類型".

但 TypeScript 可以這樣表達. 而爲了實現這個表達手法, TypeScript 多了很多概念. 你甚至可以把 TypeScript 當成一門編程語言. TS 是一門用來表達 JS 類型的編程語言.

語言意味着它有自己的語法, 編程意味着它能實現基本的編程概念, 比如 variable, function, assign, call, if else, loop 等等.

如果 TypeScript 把格局做大點, 以後任何弱類型語言想強類型化, 都可以使用它.

第三個階段就是類型體操

如果你只是寫業務代碼, 你不會上升到第三階段. 因爲沒有需求. 但如果你開發 library 就會.

許多語言 (C#) 的 library 都挺可憐的. 一堆的方法重載. 一堆的強轉類型. 這是因爲語言表達力不夠. 

如果有關注 C# 就會發現, 自從 Blozar 想喫前端蛋糕後, C# 加入了許多特性. 就是因爲 C# 沒辦法直接替代 JS, 

在第二階段我們會用到許多 TypeScript Utility 來表達類型間的邏輯關係, 這些 build-in 的 Utility 是 TypeScript 封裝給我們的.

它底層其實是 TypeScript 編程 e.g. variable, function, assign, call, if else, loop

到了第三階段, build-in 的 Utility 就不夠用了. 我們需要自己編寫 Utility (類型體操) 和使用 Utility Library e.g. type-festts-toolbelt.

這階段你就需要對 TS 編程的部分非常瞭解了.

 

表達與理解

語言嘛, 就是表達和理解, 2 個點.

我們主要都是在學表達的部分, 因爲理解是解析器搞的事情, 但是呢, 作爲表達者, 我們多少也需要去了解 "聽者" 是否明白我們說的哦.

表達

let value: string;

作爲表達方, 上面這一句想說的是, 1 個 variable 它的類型是 string.

理解

而作爲理解方. 當 variable 的 value 不是 string 時, 必須要警示.

同時, 在編程時, 需要提供 intellisense (help tips)

整個過程就是, 我們表達類型 > IDE 理解類型 > 並做出後續的補助. 這個循環就起來了.

語言的發展與侷限

在寫 TypeScript 的時候, 我們會有以下幾個感受, 這些都是因爲 TypeScript 還不夠完善.

1. 表達不出來. (感受: 邏輯編不出...只能 hardcode)

2. 理解不對. (感受: er... 難道我說的不對?)

3. 不夠聰明. (感受: 笨, 不是告訴你了嗎, 怎麼還不會?)

遇到的時候不要慌, 上 Github Issue 看看高 thumb 的 feature request. 可能會有 roadmap 或 workaround.

 

類型與 Annotations (聲明類型的方式)

我們一邊學類型, 一邊學如何聲明類型吧.

Type Annotation of Variables

從最簡單的開始, 給變量聲明類型

const value1: string = '';
相等於 C# 的
string value1 = "";

如果 value 不是 string 那麼 IDE 就會提示錯誤.

JavaScript 類型

之前複習 JS 時, 我寫了一篇 JavaScript – 數據類型.

JS 有 8 個類型, string, number, boolean, object, null, undefined, bigint, symbol

通過 typeof 可以查出來, 除了 null 因爲歷史原因被錯誤的當成 object, 其它都很好理解. bigint 是 es2020 才加入的.

const value1: string = '';
const value2: number = 0;
const value3: boolean = false;
const value4: object = Object.create(null);
const value5: null = null;
const value6: undefined = undefined;
const value7: bigint = 0n;
const value8: symbol = Symbol();

JS 的類型 TypeScript 都支持.

null is null not object

TS 糾正了 JS null is object 的混亂, null 就是 null, value 只能是 null

object vs Object

參考: Stack Overflow – Difference between 'object' ,{} and Object in TypeScript

object 類型其實不是我們直觀理解的對象哦, 它其實表示 "除了其它 7 個類型以外的類型"

但爲什麼 object 不是 Object 呢? 首先你要了解 JS 的對象, 看這篇 JavaScript – 理解 Object, Class, This, Prototype, Function, Mixins

const obj1: Object = {};
const obj2: Object = new Object();
const obj3: Object = Object.create(Object.prototype);
const obj4: object = Object.create(null); // 它是 object not Object

obj 1,2,3 是等價的. 創建出來的對象 __proto__ 都指向 Object.prototype. 所以這個對象可以調用所有 Object.prototype 的方法 (e.g. hasOwnProperty)

但是 obj4 不同, 它的 __proto__ 是 null, 所以無法調用 Object.prototype 的方法.

而 TS 在這裏就做了區分. Object 表示對象有原型鏈對接 Object.prototype. 它可以調用 Object.prototype 的所有方法擁, 而 object 卻沒有.

這就是它們的區別了.

Type Annotation of Function Parameters and Return

聲明函數參數和返回值的類型

function doSomething(param1: string, parma2: number): string {
  return 'value';
}

Type Annotation of Class Property and Method

class Person {
  constructor(age: number) {
    this.age = age;
  }
  name: string = '';
  age: number;

  method(param1: string): string {
    return '';
  }
}

More than JS 類型

除了 JS 的類型外, TS 擴展了很多很多的類型

void

C# 用 void 來表示函數沒有返回值.

JS 的函數沒有返回的情況下, 依然會返回 undefined.

早年我們也用 void 0 來表示 undefined. 

所以 TypeScript 就用了 void 來表示函數沒有返回值.

const value1: undefined = void 0; // 賦值 void 0 到類型 undefined 是可以的
const value2: void = undefined; // 賦值 undefined 到類型 void 是可以的
function doSomething(): void {}

any

顧名思義, any 表示它是任何類型. 什麼都能接受.

const value1: any = '';
const value2: any = 0;
const value3: any = false;

不管 value 是什麼都可以放入 any 類型裏. 

any 是最被濫用的 TypeScript 類型. 尤其是遇到 JS convert to TS 的項目. 爲了 by pass error 經常會亂用 any. 以至於大家把這種代碼稱作 AnyScript...

一個好的 TS 代碼要儘可能少的使用 any 類型.

unknown

由於 any 太亂來了. 於是 TypeScript 引入了一個叫 unknown 的類型. unknown 常用來表示"暫時還不知道"的類型.

一旦經過類型判斷, 它就不在是 unknown 了.

function doSomething(value: unknown): string {
  // 一開始還不知道 value 類型, 所以 value 什麼方法也沒有.
  if (typeof value === 'string') {
    // 經過判斷, 已知 value 是 string, 於是可以調用 string 方法
    return value.substring(0);
  } else if (typeof value === 'number') {
    // 經過判斷, 已知 value 是 number, 於是可以調用 number 方法
    return value.toString();
  } else {
    return 'value';
  }
}

any vs unknown

const any: any = 'whatever type value';
// 因爲是任何類型, 所以啥都可以調用, 不報錯
any.age = 5;
any.method(); 

const unknown: unknown = 'whatever type value';
// 在不確定類似的時候, 什麼都不能調用, 會報錯
unknown.age = 5; // error
unknown.method(); // error

Array

const values1: string[] = [''];
const values2: Array<string> = ['']; // 這個寫法和上面是等價的
values1.map(v => v); // 知道是 Array 後, 編輯器會提示 Array.prototype 的方法, e.g. map

變量函數

const method: (param1: string) => string = (value) => {
  return '';
};

函數 with args

const method: (...params: string[]) => string = (v1, v2) => {
  return '';
};

constructor able (class)

const personClass: new (...args: unknown[]) => void = class Person {};

 

類型管理和約束

除了類型, TS 也提供了許多額外的類型管理和約束特性. 這些都是 C# / Java 有的.

Abstract

abstract class Person {
  abstract method(param1: string): string;
  name: string = '';
}

class Ali extends Person {
  method(param1: string): string {
    return '';
  }
}

抽象 class 表示這個類不能直接被 new, 只能被其它 class 繼承.

抽象方法表示父類只聲明類型, 具體實現代碼交由子類完成.

Access Modifiers

 

 

 

 

 

 

, Method Overload

 

 

 

Interface, 泛型

Interface, Access Modifiers, Abstract, 泛型 C# / Java 都有, 這些不完全算是類型, 但是和類型息息相關. 所以 TS 也是具備的.

Interface 接口

class 通過 "implements" 聲明接口, 這個是 Java 的語法. 

class 可以聲明多接口, 不像 extends 只能單繼承.

interface CanDoSomething {
  name: string;
  doSomething(param1: string): string;
}

interface CanFly {
  fly(param1: string): void;
}

class Person implements CanDoSomething, CanFly {
  name: string = '';
  fly(param1: string): void {}
  
  doSomething(param1: string): string {
    return '';
  }
}

interface extends interface

接口是可以繼承的, 而且可以 multiple inherit 哦

interface Parent1 {
  name: string;
}

interface Parent2 {
  age: number;
}

interface Child extends Parent1, Parent2 { // multiple extends
  fly(param1: string): void;
}

class Person implements Child {
  name: string = '';
  age: number = 0;
  fly(param1: string): void {}
}

interface for function

 

 

 

 

 

 

 

 

 

 

Union Types (聯合類型)

聯合類型帶有 "或者" 的概念. 比如 value is string or number

const value1: string | number = ''; // string 可以接受
const value2: string | number = 0; // number 也可以接受

function doSomething(value: string | number) {
  value.toString(); // 在沒有進一步判斷類似時, 編輯器只會顯示 string 和 number 共同擁有的方法, 比如 toString, valueOf
  if (typeof value === 'string') {
    value.substring(0); // 進一步判斷後就可以使用 string 方法了.
  }
}

intersection Types (交叉類型)

交叉類型帶有 "和" 的概念, 比如 value is Person and Animal

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章