---
# System prepended metadata

title: TYPESCRIPT

---

# TYPESCRIPT

此筆記是根據這門課程所製作而成的
https://www.udemy.com/course/understanding-typescript/

> [name=Layors] from 2022/10/11

Typescript是JavaScript的超集
TypeScript並不能像JavaScript一樣被瀏覽器所執行，也無法被node.js執行
->TypeScript會把自己編譯回JavaScript檔

TypeScript追加了JavaScript沒有的型別
如果型別不對會報錯，但一樣會把js檔編譯出來
typeof可以用來確認某變數的型別，而這是js的東西
而且他在執行時才會作用，而非編譯時

## Basic & type

TS給予型別的方式:在變數後面加冒號再加型別
```typescript=
function add(n1: number, n2: number) {
    return n1 + n2;
}
```
### core type
通通都寫小寫

在建立變數時，若已賦值則通常不會在變數後面加型別
如果只有宣告而未賦值，則可加入型別
- number
  沒有分整數或浮點數，就是number
- string
  可以用'', "", \`\`來包
- boolean
  沒有truthy跟falsy
> [name=Layors] from 2022/10/12
- object
  可以只標示object，告訴TS這是某個object
  或是可以標示整個完整的object，告訴TS裡面有什麼property
  ```typescript=
  const person: {
    name: string;
    age: number;
  } = {
    name: 'me',
    age: 23
  }
  ```
- Array
  Array裡的型別可以鬆散也可以嚴謹
  ```typescript=
  let activity: string[];//裡面只能存string
  let activity: any[];//裡面可以存各種型別
  ```
- Tuple
  固定長度、固定型別的Array
  ```typescript=
  let lottery: [number, number];//裡面只能有兩個數字，不能少也不能多
  lottery = [1, 2, 3];//會報錯

  lottery = [1, 2];
  lottery.push(3);//用push的話TS偵測不到錯誤
  ```
- Enums
  enumerate 列舉
  ```typescript=
  enum Role {
    ADMIN,
    READ_ONLY,
    AUTHOR,
  }

  console.log(Role.ADMIN);//0
  console.log(Role.READ_ONLY);//1
  console.log(Role.AUTHOR);//2
  ```
  數字可以自己更改
  ```typescript=
  enum Role {
    ADMIN = 5,
    READ_ONLY, //6
    AUTHOR, //7
  }
  
  ...
  
  enum Role {
    ADMIN = 5,
    READ_ONLY = 7,
    AUTHOR = 9,
  }
  ```
  也可以不用數字，使用自訂的值
  ```typescript=
  enum Role {
    ADMIN = 'me',
    READ_ONLY = 'you',
    AUTHOR = 'him',
  }
  ```
- any
  不受限制
  
### 進階type
- union
  讓一個變數可以接受多種型別
  但有可能會報錯
  所以可能會要搭配runtime check的typeof
  ```typescript=
  function combine(input1: number | string, input2: number | string) {
    let result;
    if (typeof input1 === "number" && typeof input2 === "number") {
      result = input1 + input2;
    } else {
      result = input1.toString() + input2.toString();
    }
    return result;
  }
  ```
- literal type
  特定形式的基本型別
  ```typescript=
  //物種只能傳入cat或dog，而這兩個都是string，只是被限制只能用這兩個string
  function animal(name: string, species: 'cat' | 'dog')
  ```
- type aliases/ custom type
  自訂型別
  ```typescript=typescript=
  type combinable = number | string; //可接受number跟string
  type twoSpecies = 'cat' | 'dog'; //literal type
  type User = {
    name: string;
    age: number;
  }; //object，先定義好有助於減少不必要的重複
  
  ...
  
  const user1: User = { name: 'Me', age: 23 };
  ```
- function return type
  意思就是return的值的type
  ```typescript=
  function add(n1: number, n2: number): number {
    return n1 + n2;
  }//return type加在function參數括號後面
  
  function printIt(n1: number) {
    console.log("It is " + n1);
  }//沒有return任何東西，return type為void
  ```
  假如我們console.log沒有return任何值的function，會得到undefined，同時undefined也是TS的其中一個型別
  但要有return東西的function才能把return type設成undefined
  所以沒有return任何東西的function要用void
- function
  JS可以把function存在一個變數裡，為了怕他被其他不function的值存進去，所以有function type
  ```typescript=
  let combine: Function;// F要大寫
  combine = add;
  combine = printIt;
  ```
  但依然會被其他function蓋掉，所以可以更進一步的指定傳入的值、輸出的值的型別
  ```typescript=
  let combine: (a: number, b: number) => number;// 用箭頭函式
  ```
- callback
  ```typescript=
  function addAndPrint(n1: number, n2: number, cb: (num: number) => void)
  //這裡的void代表這個cb不會對傳來的值做任何使用
  //即使用到的cb有傳值過來也不會報錯
  
  ...
  
  addAndPrint(10, 20, (num) => { //這裡的num不用加type是因為在上面已經定義過這裡的num會是什麼型別了
    console.log(num);
  });
  ```
- unknown
  跟any很像，但unknown type的變數不可以被賦予給其他型別的變數
  可以說是嚴格一點的any
  可以用typeof來確認unknown變數的type，再把他做處理
- never
  另一種return type
  ```typescript=
  function newError(message: string, code: number): never {
    throw { message: message, errorCode: code };
  }
  ```
  因為throw error，所以他永遠不會回傳東西，因此用never
  
## TypeScript Compiler

### 存檔後立即編譯

- 單一檔案
  watch mode
  ```bash=
  tsc app.ts --watch
  tsc app.ts -w
  ```
  進入這個模式可以讓你每次存檔TS檔之後就自動編譯
  但只能對一個檔案使用
- 整個project
  進到該project的目錄之後執行下面的程式碼
  ```bash=
  tsc -init
  ```
  執行完會建立tsconfig.json檔
  此時輸入tsc便可編譯整個project裡的ts檔
  此時可以搭配watch mode
  ```bash=
  tsc --watch
  tsc -w
  ```

> [name=Layors] from 2022/10/13
### setting
tsconfig.json告訴了typescript如何編譯ts檔
在最外面的大括號下面可以加上額外的設定
- exclude
  用array來設定，可以用特定的檔案路徑，也可以選定特定的後綴
  被選定的檔案或資料夾將不會被編譯
  ```json=
  {
    {
      ...
      ...
      ...
    },
    "exclude": [
      "example.ts", //目前路徑除外的檔案
      "testFolder", //除外的資料夾
      "*.dev.ts", //目前路徑任何後綴為.dev.ts的檔案會被除外
      "**/*.dev.ts", //任何資料夾底下的後綴為.dev.ts的檔案都會被除外
    ]
  }
  ```
  node_modules資料夾通常會被預設為除外，但在tsconfig.json裡不會被顯示出來
  但如果你在tsconfig.json有加上exclude的設定，必須要把它放進去
  不然typescript會變成不會把它除外
- include
  如果加上這個設定，則變成只有在該array裡的檔案或資料夾才會被編譯
  ```json=
  {
    {
      ...
      ...
      ...
    },
    "include": [
      "app.ts",
      "testFolder"
    ]
  }
  ```
- file
  跟include很像，但他只能指定特定檔案，不能指定資料夾
  ```json=
  {
    {
      ...
      ...
      ...
    },
    "file": [
      "app.ts",
    ]
  }
  ```

### compilerOptions
這是在tsconfig.json裡的設定選項
- target
  決定TS會被編譯成JS的哪個版本，ex:es5, es6, es2016....(es6為es2015)
- allowjs, checkjs
  allowjs為true的話會讓js檔可以被ts編譯
  checkjs為true的話js檔不會被ts編譯，但會檢查語法是否正常
- sourceMap
  按F12並切換到source，可以看到我們的檔案，但只會看到js檔，而非ts檔
  當sourceMap為true時，編譯後會產生app.js.map檔
  在F12 source裡可以看到ts檔，也可在那裡設置斷點
- rootDir, outDir
  一般來說，ts編譯完的js檔會被放在同一目錄下
  而設置outDir可以指定編譯完的js檔該放在哪
  而他也會複製資料夾結構
  ```json=
  "outDir": "./jsFile",
  ```
  ![](https://i.imgur.com/aV9L2IH.png)
  但如上所示，outDir裡並沒有src資料夾
  因為我們並沒有設置rootDir，因此他自動把src當根目錄
  如果我們在外面又新增新的ts檔，他會重新選最外面的路徑為rootDir
  因此一開始要設定好
  ```json=
  "rootDir": "./",
  ```
  rootDir必須包含所有的ts檔，不然會報錯
- removeComment
  js檔裡將不會有註解
- noEmit, noEmitOnError
  noEmit為true時不會產出js檔
  noEmitOnError為true時在compile失敗時將不會產出js檔
- strict
  在compilerOptions裡有一個Type Checking的區塊
  如果strict設為true，則下面到alwaysStrict的選項就會被設為全開
  如果沒有設為true，則要一個一個個別調整
- noUnusedLocal
  若設為true，如果有未使用到的local變數，會報錯
- noImplicitReturns
  若設為true，如果有function不一定會return值回來，會報錯

## class

### constructor
```typescript=
class Department {
  name: string;
  
  constructor(n: string) {
    this.name = n;
  }
}

const development = new Department("development");
```

### method
```typescript=
describe(){
  console.log('Department: ' + this.name);
}
```

### access modifier
private, public, protected
public是預設的
我們不希望一些資料暴露給別人，因此會用private
private可以用在property跟method上
```typescript=
class Department {
  name: string;
  private employees: string[] = [];
  
  constructor(n: string) {
    this.name = n;
  }
  
  addEmployee(employee: string) {
    this.employees.push(employee);
  }
}

const development = new Department("development");

development.addEmployee("a");
development.addEmployee("b");
development.addEmployee("c");

empployees[0] = 'me'//若要嘗試修改private的東西會報錯
```

### shortcut for initialization
```typescript=
class person {
  private name: string;
  private age: number;
  private job: string;
  
  constructor(n: string, age: number, job: string) {
    this.name = n;
    this.age = age;
    this.job = job;
  }
}
```
太繁瑣了，因此有簡化的方式:
```typescript=
class person {
  constructor(private name: string, private age: number, public job: string) {
  }
}
```
在每個變數的前面加上access modifier，他就會自動幫你產生跟變數名字一樣的property

### readonly
如果你一些property不想之後被更改，可以在access modifier後面加上readonly
```typescript=
private readonly name: string;
```

### inheritance
```typescript=
class Department {
  private employees: string[] = []
  constructor(private id: string, private name: string) {
  }
  
  describe(){
    console.log('Department: ' + this.name);
  }
}

class ITDepartment extends Department {
  constructor(id: string, private admins: string[]) {
    super(id, 'IT');
  }
}
```
在這裡，ITDepartment可以使用describe，因為他是public method
而他無法使用到employees，因為該property是private
使用private的話就只能在那個class裡使用，就算是繼承過來的也無法使用
此時可以改用protected，可以讓繼承的class用到該property的同時也保持不被外部更動

### getter, setter
```typescript=
class Department {
  private employees: string[] = ['john', 'jane']
  
  get employeeList(){
    return this.employees;
  }
  
  set employeeList(name: string[]) {
    this.employees = name;
  }
  
  constructor(private id: string, private name: string) {
  }
}

const development = new Department("development");
console.log(development.employeeList)
development.employeeList = ["me"];
console.log(development.employeeList);
```
- getter
  裡面一定要有return
  當呼叫getter的時候後面不用加()
  通常用在你想要對傳回去的資料做一些處理的時候
- setter
  括號裡面一定要有參數
  當呼叫setter時一樣不用加()，用類似把值指定給變數的方式使用
  
> [name=Layors] from 2022/10/14
### static method & property
不必新建一個該class的object就能從class裡呼叫
用法: 在method或property前面加上static
```typescript=
class Department {
  
  //static property
  static establishedAt: number = 2022;
  
  //static method
  static createEmployee(name: string){
    return {name: name};
  }
  
  constructor(private id: string, private name: string) {
    //不能用this，下面這段code會報錯
    console.log(this.establishedAt);
    //要用class的名字來呼叫
    console.log(Department.establishedAt);
  }
}

console.log(Department.establishedAt);
const newEmployee = Department.createEmployee("me");
```
在class裡面是不能用this來呼叫static property跟static method的

### abstract class
當被繼承的class的method, property沒辦法提供一個default，希望繼承你的class都強制要override他時，用abstact
此時class跟該method, property前面都要加上abstract，method後面不加上{}，property不賦予初始值
```typescript=
abstract class Department {
  abstract target: string;
  constructor(private id: string, private name: string) {
  }
  
  abstract describe();
}
```
當class為抽象時，無法建立該class的object

### private constructor & singleton pattern
當你確定用這個class建立出來的object只會有一個的時候，會用到singleton pattern
用法為在constructor前面加上private，這樣做之後你就沒辦法用new建立該class的object

如何跟他互動?
->static method
```typescript=
class JohnDoe {
  //用來儲存這個object
  private static instance: JohnDoe;

  private constructor() {
    
  }
  
  static getInstance() {
    if (JohnDoe.instance) {
      //回傳這個object
      return this.instance;
    } else {
      // 若還沒建立則new一個JohnDoe，並回傳這個object
      this.instance = new JohnDoe();
      return this.instance;
    }
  }
}

//下面這段code會報錯
const John = new JohnDoe()
```
上面可以用this跟static property互動的原因是因為我們在static method裡
若在constructor裡一樣不能用this，要用class的名字來呼叫

以上面code為例，如果我們要呼叫JohnDoe，直接用John.getInstance()即可

## interface
interface用來形容一個object看起來應該要是怎麼樣
interface只存在於TypeScript，JavaScript沒有interface
interface不能給值給任何一個變數
```typescript=
interface Person {
  name: string;
  age: number;
  
  talk(phrase: string): void;
}
```
但其實custom type可以做到一樣的事
```typescript=
type Person = {
  name: string;
  age: number;
  
  talk(phrase: string): void;
}
```
interface只能用於形容一個object應該長怎樣，type則較為彈性
interface的重要點在於，他告訴了那個class一定要遵守他的規定

### class using interface
```typescript=
interface Talkable {
  name: string;

  talk(phrase: string): void;
}

class Person implements Talkable {
  name: string;
  age = 30; //可以多加interface裡沒形容的property
  constructor(name: string) {
    this.name = name;
  }

  talk(words: string) {
    console.log(words);
  }
}
```

- access modifier
  interface裡不能使用private, public或protected
  但可以使用readonly
  在interface裡使用之後不用再去class裡再次宣告，一樣會有效果
- 多重interface
  一個class可以使用多個interface
  ```typescript=
  interface Named {
    name: string;
  }

  interface Talkable {
    talk(phrase: string): void;
  }

  class Person implements Named, Talkable {
    name: string;
    age = 30;
    constructor(name: string) {
      this.name = name;
    }

    talk(words: string) {
      console.log(words);
    }
  }
  ```
- interface extends interface
  ```typescript=
  interface Named {
    name: string;
  }

  //現在如果有人要用這個interface的話，必須要有name跟talk()
  interface Talkable extends Named{
    talk(phrase: string): void;
  }
  ```
  而interface一樣可以extend多個interface
- optional parameters
  interface可以讓某些property跟method變成不是一定要實做出來的
  ```typescript=
  interface Named {
    name: string;
    age?: number;
    greet?('words'): void;
  }
  ```
  使用的方法是加上問號
  property在變數名稱後面加問號
  method在變數名稱跟括號中間加問號
  這個方法在class裡也可以使用
  ```typescript=
  interface Named {
    name: string;
    age?: number;
    greet?('words'): void;
  }
  
  class Person implements Named{
    name: string;
    age?: number;
    constructor(name: string, job?: string) {
      this.name = name;
    }
  }
  ```
- 補充:用interface做function
  ```typescript=
  interface addFunction {
    (a: number, b: number): number;
  }

  let add: addFunction;

  add = (n1, n2) => {
    return n1 + n2;
  };
  ```
  
## Advanced types

### Intersection
把兩個custom type結合在一起
```typescript=
type Admin = {
  name: string;
  privileges: string[];
};

type Employee = {
  name: string;
  startDate: Date;
};

type elevatedEmployee = Admin & Employee;

const me: elevatedEmployee = {
  name: "L",
  privileges: ["create-server"],
  startDate: new Date(),
};
```
基本上跟extends interfaces差不多
如果對兩個union type做intersection的話，會有跟使用and邏輯類似的結果
```typescript=
type a = string | boolean;
type b = number | string;

type c = a & b;//string

//補充
type d = string & number;//never
```

### Type Guard
基本上type guard就是各種確認型別的方法
- 基本型別
  ```typescript=
  type combinable = number | string;

  function add(n1: combinable, n2: combinable) {
    if (typeof n1 === "string" || typeof n2 === "string") {
      return n1.toString() + n2.toString();
    }
    //如果程式跑到這邊，那n1跟n2就一定都是number
    return n1 + n2;
  }
  ```
  上面所寫的是基本型別的type guard，但如果遇到了自訂型別會發生一些狀況
- 自訂型別
  ```typescript=
  type Admin = {
    name: string;
    privileges: string[];
  };

  type Employee = {
    name: string;
    startDate: Date;
  };

  type unknownEmployee = Admin | Employee;

  function printEmployeeInfo(emp: unknownEmployee) {
    console.log(emp.name);
    if (typeof emp === "Admin") { //TS會報錯
      console.log(emp.privileges);
    }
  }
  ```
  因為程式碼最終要編譯回JS，而JS並不懂我們的自訂型別，因此無法用typeof來確認一個變數是否為自訂型別
  因此，我們要用另一個方法:**in**
  ```typescript=
  function printEmployeeInfo(emp: unknownEmployee) {
    console.log(emp.name);
    if ("privileges" in emp) { //這樣就會過了
      console.log(emp.privileges);
    }
  }
  ```
- class
  使用的是instanceof來確認你是哪個class
  這是JS原生的功能
  ```typescript=
  class car {
    drive() {
      console.log("driving...");
    }
  }

  class truck {
    drive() {
      console.log("so heavy");
    }

    loadCargo() {
      console.log("loading...");
    }
  }

  type vehicle = car | truck;

  const v1 = new car();
  const v2 = new truck();

  function useVehicle(vehicle: vehicle) {
    vehicle.drive();
    if (vehicle instanceof truck) {
      vehicle.loadCargo();
    }
  }
  ```
  但如果有用到interface，那這方法會失效，因為這是runtime check，而interface的部分不會被編譯成JS
### Discriminated Union
一種特殊的type guard，讓你對union type用type guard時可以簡單一些
有對class的type guard，但如果是用interface就無法使用
因此有一種方法，就是在所有的interface裡加上一樣的property
並且使用switch
```typescript=
interface Bird {
  type: "bird";
  flyingSpeed: number;
}

interface Horse {
  type: "horse";
  runningSpeed: number;
}

type Animal = Bird | Horse;

function moveAnimal(animal: Animal) {
  let speed: number;
  switch (animal.type) {
    case "bird":
      speed = animal.flyingSpeed;
      break;
    case "horse":
      speed = animal.runningSpeed;
  }
  console.log("speed: " + speed);
}
```

> [name=Layors] from 2022/10/17

### Type Casting
type casting是指你自己幫助TypeScript去分辨他分辨不了的type
例如:操作DOM
```typescript=
//TypeScript不知道放在pass裡的東西到底是什麼，他只知道這是個HTML element
const pass = document.getElementById("pass")!;
```
因為TypeScript不知道你是不是會拿到東西，在這個情況下pass可能是null，在後面加!等於告訴TypeScript他抓到的東西永遠不會是null

有兩種解決方法
1. ```typescript=
   const pass = <HTMLInputElement>document.getElementById("pass")!;
   ```
   在前面加上<>，並在裡面寫明這是什麼東西
   這個有點像是react的方法
2. ```typescript=
   const pass = document.getElementById("pass")! as HTMLInputElement;
   ```
   
### Index
```typescript=
interface ErrorContainer{
  email: string
  [Prop: string]: string;
}

const err: ErrorContainer = {
  email : 'invalid email',
  name: 'invalid name'
}
```
上面的code意思是這個object的key一定要是string，而key裡存的值也是string
email: string的意思是你先預設了這個object裡一定會有一個叫email的key
之後在建立這個物件的時候就一定要寫出他，不然會報錯

### Function Overload
```typescript=
type combinable = number | string;

function add(n1: number, n2: number): number
function add(n1: combinable, n2: combinable) {
  if (typeof n1 === "string" || typeof n2 === "string") {
    return n1.toString() + n2.toString();
  }
  return n1 + n2;
}
```
根據變數的型別、數量不同，做出不同類型的return，但function名字都一樣
假如沒做function overload，使用add function的結果都會被TypeScript識別為combinable
而有做的話，可以被識別為各種型別
!!!但要注意，一旦做了function overload，下面function的implementation會變得無法呼叫，你只能去呼叫已經宣告的function overload
像上面的例子，如果我們把兩個string丟進add裡，TypeScript會報錯，因為你只有寫出當兩個變數都是number時會return什麼，還沒寫出丟兩個string進去會發生什麼
```typescript=
function add(n: number): number
function add(n1: number, n2: number): number
function add(n1: number, n2?: number) {
  if(n2 !== undefined){
    return n1 + n2; 
  }
  return n1;
}
```

### optional chaining
當你在別的地方(ex:database)取得了一個object，但你不知道這個object裡的某個property是否存在，Typescript不會報錯，因為他偵測不到
這時可以用option chining
```typescript=
type JobData = {
  title: string;
  desc: string;
};

type Person = {
  name: string;
  age?: number;
  job?: JobData;
};

const John: Person = {
  name: "John",
  // age: 23,
  // job: { title: "paladin", desc: "May light blesses you" },
};

console.log(John?.age);
console.log(John?.job?.title);
```
先定義好取出來的資料的結構，可能會沒define到的property加上問號
之後要用到該property時，在該object的後面加上問號，如果該property不存在，不會產生runtime error而跳出程式

### nullsh coalescing
```typescript=
const input = "";

const stored = input || "default value";

console.log(stored);
```
如果你有一個input你不知道他是null, undefined或是有東西，用上面的程式可以讓你只在input有值的時候做操作，但如果是空字串或是數字0，這也會被歸類在falsy value
但如果我們想要保存空白input或數字0，要用另一種方式:??
```typescript=
const input = "";

const stored = input ?? "default value";

console.log(stored);
```

**NEXT**
> [TYPESCRIPT](/-u29b5iuS8GMMwrlaCirEQ)