---
# System prepended metadata

title: TypeScript 學習筆記
tags: [Programing, ' Typescript']

---

---
tags: Programing, Typescript
---
# TypeScript 學習筆記

[![hackmd-github-sync-badge](https://hackmd.io/Z1bsxv0jSSWEQ8_rwXGN7A/badge)](https://hackmd.io/Z1bsxv0jSSWEQ8_rwXGN7A)

[TOC]

# `第一章` Basic types
# `第二章` Interfaces
# `第三章` Funcions
# `第四章` Literal Types
# `第五章` Union and Intersection Types

<font size=5>透過Intersection 以及 Union 
可以提供一個方式使我們能夠組成types</font>

## Union
<font size=5>當遇到一個函式需要number以及string時，如:</font>
```typescript=
/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */
function padLeft(value: string, padding: any) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

padLeft("Hello world", 4); // returns "    Hello world"
```
<font size=5>
在上面的狀況下，若傳入的 padding 型別不是 number 或 string 仍然有效
改法如下
</font>

```typescript=
/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

padLeft("Hello world", 4); // returns "    Hello world"
```

## Unions with Common Fields

<font size=5>
若有一個value為union type，我們只能存取這些type共同的members
</font>

```typescript=
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

declare function getSmallPet(): Fish | Bird;

let pet = getSmallPet();
pet.layEggs();

// Only available in one of the two possible types
pet.swim(); //Error happended
```

## Discriminating Unions
```typescript=
type NetworkLoadingState = {
  state: "loading";
};

type NetworkFailedState = {
  state: "failed";
  code: number;
};

type NetworkSuccessState = {
  state: "success";
  response: {
    title: string;
    duration: number;
    summary: string;
  };
};

// Create a type which represents only one of the above types
// but you aren't sure which it is yet.
type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;
```
<font size=5>
在上面我們定義出三種State的type <br>
並且他們有一個共同的元素 state <br>
在這之後我們可以透過當前state的值來得知現在狀態為何
</font>

```typescript=
type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;

function networkStatus(state: NetworkState): string {
  // Right now TypeScript does not know which of the three
  // potential types state could be.

  // Trying to access a property which isn't shared
  // across all types will raise an error
  state.code; //error
  
  // Property 'code' does not exist on type 'NetworkState'.
  // Property 'code' does not exist on type 'NetworkLoadingState'.


  // By switching on state, TypeScript can narrow the union
  // down in code flow analysis
  switch (state.state) {
    case "loading":
      return "Downloading...";
    case "failed":
      // The type must be NetworkFailedState here,
      // so accessing the `code` field is safe
      return `Error ${state.code} downloading`;
    case "success":
      return `Downloaded ${state.response.title} - ${state.response.summary}`;
  }
}
```

## Union Exhaustiveness Checking
<font size=5>
當我們新增union type的型別時
我們會希望compiler能告訴我們曾經使用到的地方需要更新
<br>
<br>
且若沒有使用到時，傳的值應該會與原先定義的不同
</font>

```typescript=
type NetworkFromCachedState = {
  state: "from_cache";
  id: string
  response: NetworkSuccessState["response"]
}

type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState
  | NetworkFromCachedState;

function logger(s: NetworkState) {
  switch (s.state) {
    case "loading":
      return "loading request";
    case "failed":
      return `failed with code ${s.code}`;
    case "success":
      return "got response"
  }
}
```
<font size=5>
透過never型別來偵測型別完不完整
</font>

```typescript=
function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function logger(s: NetworkState): string {
  switch (s.state) {
    case "loading":
      return "loading request";
    case "failed":
      return `failed with code ${s.code}`;
    case "success":
      return "got response";
    default: 
      return assertNever(s)
  }
}
```

## Intersection Types
<font size=5>
Intersection 語法為 & <br>
將多個type 合成為一個 大type <br>
且每個type有的member 大type都得涵蓋住 <br>
底下則是把共同的type取出來<br>
使得單元變得更小，模組化更容易<br>
</font>

```typescript=
interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

interface ArtistsData {
  artists: { name: string }[];
}

// These interfaces are composed to have
// consistent error handling, and their own data.

type ArtworksResponse = ArtworksData & ErrorHandling;
type ArtistsResponse = ArtistsData & ErrorHandling;

const handleArtistsResponse = (response: ArtistsResponse) => {
  if (response.error) {
    console.error(response.error.message);
    return;
  }

  console.log(response.artists);
};
```

# Class

## Classes
<font size=5>
typescript 也支援Class 
</font>

```typescript=
class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");
```

## Inheritance
<font size=5>
觀念相同就不花篇幅說了
</font>

```typescript=
class Animal {
  move(distanceInMeters: number = 0) {
    console.log(`Animal moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof! Woof!");
  }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
```

## Public, private, and protected modifiers
### public by default
<font size= 5>
在上面的篇幅中，Class沒有特別宣告public<br>
但如果需要的話，仍然可以在變數前方增添public keyword
</font>

```typescript=
class Animal {
  public name: string;

  public constructor(theName: string) {
    this.name = theName;
  }

  public move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}
```

### ECMAScript Private Fields

<font size=5>
在 TypeScript 3.8 支援最新的JavaScript語法定義 private
</font>

```typescript=
class Animal {
  #name: string
  constructor(theName: string) {
    this.#name = theName
  }
}

new Animal("Cat").#name
```

### Understanding TypeScript's `private`

<font size=5>
TypeScript 本身提供關鍵字 private 來宣告私有變數
</font>

```typescript=
class Animal {
  private name: string;

  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name;
```

<font size=5>
private Class 繼承實例
</font>

```typescript=
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

class Rhino extends Animal {
  constructor() {
    super("Rhino");
  }
}

class Employee {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // 會出錯 兩者皆有自己的 name
```


### Understanding `protected`

<font size=5>
宣告為protected的變數<br>
在繼承後的subclass能取出<br>
</font>

```typescript=
class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name);
```

### Readonly modifier
<font size=5>
Readonly 變數在initial的時候就應該賦予值<br>
只有在 宣告時 以及 constuctor() 內可以賦予<br>
而 Readonly 的變數被視為public的 <br>
若要私有化必須加 private 
</font>

```typescript=
class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;

  constructor(theName: string) {
    this.name = theName;
  }
}

let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit";// 出錯
```

### Parameter properties

<font size=5>
我們可以在constructor的參數名加上readonly<br>
這樣的話我們就可以減少原先版本宣告的那行了
</font>

```typescript=
class Octopus {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {}
}

let dad = new Octopus("Man with the 8 strong legs");
dad.name;
```

### Accessors

<font size=5>
描述有關get() 跟 set()
</font>

```typescript=
const fullNameMaxLength = 10;

class Employee {
  private _fullName: string = "";

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (newName && newName.length > fullNameMaxLength) {
      throw new Error("fullName has a max length of " + fullNameMaxLength);
    }

    this._fullName = newName;
  }
}

let employee = new Employee();
employee.fullName = "Bob Smith";

if (employee.fullName) {
  console.log(employee.fullName);
}
```

### Static Properies
<font size=5>
`Static` 類別的靜態屬性

類別的靜態屬性與方法
不需要經由建構物件的過程
而是直接從類別本身提供的屬性與方法
稱之為靜態屬性與方法
或者被稱為靜態成員（Static Members）
具有固定、單一版本、不變的原則

通常會使用靜態成員的狀況：
    
1. 靜態成員不會隨著物件建構的不同而隨之改變
        
2. 靜態成員可以作為類別本身提供的工具，不需要經過建構物件的程序；換句話說：類別提供之靜態成員本身就是可被操作的介面


</font>

```typescript=
class Grid {
  static origin = { x: 0, y: 0 };

  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }

  constructor(public scale: number) {}
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));

```

### Abstract Classes

<font size=5>
描述Class藍圖不需要特地實作
</font>


```typescript=
abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log("roaming the earth...");
  }
}
```

### Advanced Techniques

#### Constructor Functions

<font size=5>
Because classes create types, you can use them in the same places you would be able to use interfaces.
</font>

```typescript=
class Point {
  x: number;
  y: number;
}

interface Point3d extends Point {
  z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };
```

# Enums

## Numeric enums
<font size=5>
常見於其他程式語言中
</font>

```typescript=
enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}
```
<font size=5>
在使用上也十分容易
</font>

```typescript=
enum UserResponse {
  No = 0,
  Yes = 1
}

function respond(recipient: string, message: UserResponse): void {
  // ...
}

respond("Princess Caroline", UserResponse.Yes);
```

## String enums
<font size=5>
觀念相同，只是需要傳入的值為string
</font>

```typescript=
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}
```

## Heterogeneous enums
<font size=5>
enums 可以把上面的混用，但這樣的行為不建議
</font>

```typescript=
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES"
}
```

## Computed and constant members
<font size=5>
如果enum的第一個元素沒有initializer 則，他的值會為0
</font>

```typescript=
// E.X is constant:
enum E {
X
}
// All enum members in 'E1' and 'E2' are constant.

enum E1 {
X,
Y,
Z
}

enum E2 {
A = 1,
B,
C
}
```
<font size=5>
It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity.
</font>
```typescript=
enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length
}
```
## Union enums and enum member types
<font size=5>
用Enum來當Type
</font>

```typescript=
enum ShapeKind {
  Circle,
  Square
}

interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}

interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}

let c: Circle = {
  kind: ShapeKind.Square, // 將會出錯, kind應該是Circle
  radius: 100
};

```
## Enums at runtime
## Enums at compile time 
<font size=5>
用keyof typeof來獲得Enum的Type時，會發現全部的keys都用string表示
</font>

```typescript=
enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG
}

/**
 * This is equivalent to:
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {
  const num = LogLevel[key];
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key);
    console.log("Log level value is:", num);
    console.log("Log level message is:", message);
  }
}
printImportant("ERROR", "This is a message");
```
## Reverse mappings
<font size=5>
除了順向的從Enum取值外，也可以逆向的傳值獲得key的名稱
</font>

```typescript=
enum Enum {
  A
}

let a = Enum.A;
let nameOfA = Enum[a]; //'A'
```

<font size=5>
Keep in mind that string enum members do not get a reverse mapping generated at all.
</font>
## const enums
## Ambient enums
<font size=5>
奇葩的寫法XD
</font>

```typescript=
declare enum Enum {
  A = 1,
  B,
  C = 2
}
```

# Generics

## Hello World of Generics
<font size=5>
很久之前看到的東西了，基本上他會去抓取使用者傳進來的參數type<br>
然後create出一個同樣參數型別的function
</font>

```typescript=
function identity<T>(arg: T): T {
  return arg;
}
```

<font size=5>
上面這段函式，只要使用者輸入甚麼型別，他回傳的就是該型別<br>
因此可以方便我們去傳遞從其他地方來的型別成為我們要的<br>
以下為實際應用狀況
</font>

```typescript=
let output = identity<string>("myString");
//       ^ = let output: string
```

```typescript=
let output = identity("myString");
//       ^ = let output: string
```
## Working with Generic Type Variable
## Generic Types

```typescript=
interface GenericIdentityFn {
  <T>(arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn = identity;
```

## Generic Classes

```typescript=
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
  return x + y;
};
```

## Generic Constraint
<font size=5>
當我們要對Generic進行限制時，可透過建立出擁有必要元素的interface <br>
再透過Extends 去繼承他，如此一來便可以篩除不符合條件的參數了
</font>

```typescript=
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // Now we know it has a .length property, 
  // so no more error
  return arg;
}
```

## Using Type Parameters in Generic Constraints
<font size=5>
底下這個比較難理解，首先我們知道傳入的參數一個是Object 另一個則是 key <br>
因此在Generic 參數設定上, K extends T的key們 <br>
因此K的型別會屬於T這個Object內容物的型態 <br>
因此可確保傳入的obj以及key都是有效參數 <br>
</font>

```typescript=
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

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

getProperty(x, "a");
// getProperty(x, "m");
```
