2021 第47週 學習記錄:TypeScript

總結

本週重心放在理解公司專案結構以及 TypeScript Generics、Utility types、Basic types 中的anyunknownnevervoid以及typeinterface之間的比較,筆記以 TS 相關內容為主

Generic

Generic Types

Generic classes

Generic classes are only generic over their instance side rather than their static side, so when working with classes, static members can not use the class’s type parameter.

Class: static keyword

Static methods are often utility functions, such as functions to create or clone objects, whereas static properties are useful for caches, fixed-configuration, or any other data you don’t need to be replicated across instances.

簡單記法:有static關鍵字的話,可以直接透過該 class 取用,不用先new一個該 class 的 instance 出來(如以下示範碼的displayNamelogDisplayName());如果沒有static的話,必須透過該 class 的 instance 才能操作(如以下示範碼的bark()

class Dog {
  static displayName = 'A Dog.';

  static logDisplayName() {
    console.log(this.displayName);
  }

  bark() {
    console.log('Bark!');
  }
}

let d = new Dog();

d.bark(); // 'Bark!'

Dog.logDisplayName(); // 'A Dog.'
Dog.bark(); // Error: Dog.bark is not a function

Generic Constraints

簡單總結:透過關鍵字extends對傳進來的參數的<T>做出一些限制

interface Lengthwise {
  length: number;
}

// 確保傳進來的參數一定會有length這個property
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

loggingIdentity(3); // 會報錯,'3'並沒有length這個property
loggingIdentity({ length: 10, value: 3 }); // 這樣OK,因為傳入的參數有length這個property

組合技:

function getProperty<T, Key extends keyof T>(obj: T, key: Key) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, 'a'); // OK
getProperty(x, 'm'); // 這個會報錯,因為傳入的obj裡面沒有m這個key

解說:

  1. 透過keyof取傳入的物件參數的所有 keys
  2. 型別變數<key>為「物件參數所有 keys 的延伸」
  3. 對於傳入的key參數的限制為「只能是該物件參數的 keys」

keyof Type Operator

The keyof operator takes an object type and produces a string or numeric literal union of its keys.

簡單來說:抓某物件的 keys 作為 type

type Point = { x: number; y: number };
type P = keyof Point; // P會是 'x' | 'y'

let p: P = 'x'; // 變數p只能是'x'或'y',其他都會報錯

typeof Type Operator

Specifically, it’s only legal to use typeof on identifiers (i.e. variable names) or their properties.

簡單來說:把它變成 type

function f() {
  return { x: 10, y: 3 };
}

// 這樣會報錯
type P = ReturnType<f>;

// 要這樣寫,透過typeof取出f的type
// type P = { x: number; y: number; }
type P = ReturnType<typeof f>;

type any

type unknown

The unknown type is a lot safer than any because unknown forces us to do additional type-checking to perform operations on the variable.

type never

type void

void !== undefined

When a function’s return value set as void, this also means that don’t use the return value of this function.

declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;

let target: number[] = [];
forEach([1, 2, 3], (el) => target.push(el));
// 這樣會報錯:Type "number" is not assignable to type "undefined"
// 因為Array.prototype.push()會回傳陣列的長度,陣列長度不是undefined.
// 這樣就可以
declare function forEach<T>(arr: T[], callback: (el: T) => **void**): void;

let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));

type vs interface

The key distinction is that a type can NOT be re-opened to add new properties vs an interface which is always extendable.

If you would like a heuristic, use interface until you need to use features from type.

interface: always extendable

以下寫法等於是延伸了 interface Point 的內容,最後 interface Point 的 z 值被視為 optional,故傳入的物件有無包含 z 都不會報錯:

interface Point {
  x: number;
  y: number;
}

// 可以直接沿用同一個interface,追加新的內容
interface Point {
  z?: number;
}

function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);

  if (pt.z) console.log("The coordinate's z value is " + pt.z);
}

printCoord({ x: 100, y: 100 });
printCoord({ x: 100, y: 100, z: 123 }); // 這樣不會報錯

但會建議使用 extends 關鍵字來延伸 interface 內容,增加閱讀性,也減少意外覆蓋的可能性

type: can NOT be re-opened

以下這樣會報錯:Duplicate identifier 'Animal'.,type 變數不可重複 assign value:

type Animal = {
  name: string;
};

type Animal = {
  species: string;
};

Custom primitives name (type only)

Interfaces may only be used to declare the shapes of objects, can NOT rename primitives.

// 可以透過type來為primitive type自訂新名稱
type SanitizedString = string;
type EvenNumber = number;

// 這樣不行,會報錯
interface X extends string {
  // ...
}

IDE 中是否顯示名稱

demo

參考文件