# Typescript

教學連結 TypeScript TS 快速入門|Tiktok工程師帶你入門前端|布魯斯前端 (opens new window)

# 基本類型

// ---- 基本類型 ----

// 當有賦值時不需要定義變數類型
let str = 'bruce';

// 若未賦值時則需定義變數類型
let str1: string;
str1 = 'bruce2';

let num = 1000;
let boo = true;

// 因筆記方便故定義變數類型與復值
let n: null = null;
let un: undefined = undefined;

// 寫 any 等於沒寫 =)
let test: any = 1

# 陣列

// ---- Array ----
let arr: string[] = ['a', 'b', 'c']

// 二維陣列
let arr2: string[][] = [['aa', 'bb'], ['cc']]

// 元組
let tuple: [number, string, boolean] = [1, 'a', true]

// 二維元組
let tuple: [string, string][] = [['a', 'a'],['b', 'c']]

# Enum

// ---- Enum 枚舉 ----

// 開直播 api => 獲取直播狀態
// 成功 失敗 直撥中
// 0 -1 1

// 使用 Enum 來分類直播狀態,可以更直觀的閱讀程式碼
enum LiveStatus {
  SUCCESS = 0,
  FAIL = -1,
  STREAMING = 1
}

const status = LiveStatus.SUCCESS
console.log(status) // 0

# Union

// ---- Union ----
let aaa: number | string;
aaa = 123;
aaa = 'aaa';



# type

// ---- type ----
// 自定義類型,有點類似 interface
type A = number | string;
type B = boolean | string;

let a1: A;
a1 = 123;
a1 = 'aaa';

let b1: B;
b1 = true;
b1 = 'bbb'
// ---- interface 介面 ----
// 與 type 差異 => interface 為可擴充、type 不可擴充

interface User {
  name: string;
  age: number;
}


# object

// ---- object ----
// 不可擴充
type Card = {
  name: string;
  desc: string;
}

// 可擴充、可被 class 繼承
interface Card1 = {
  name: string;
  desc: string;
}

// age 可選擇顯示或不顯示
interface Card1 = {
  age?: number;
}

const obj: Card = {
  name: 'bruce',
  desc: '...',
}

// 融合兩個 interface
const obj1: Card1 = {
  name: 'bruce',
  desc: '...',
  age: 100
}

# function

// ---- function ----
// 參數
function hello (a: string, b: string) {
  return a + b
}

function hello1 (a: string, b: string): number {
  console.log(a, b)
  return 999
}

function hello2 (a: number, b: boolean, c:string): number {
  console.log(a, b, c)
  return 100
}

// undefined
// 可選參數需放在最後一個(c?: sting)
function hello3 (a: number, b: boolean, c?:string): number {
  let age: string;
  if (c === undefined) return -1 // 解法:直接在這邊下判斷
  age = c; // c 可能為 string | undefined 則會報錯
  return 100
}

// 箭頭函式
const func = () => {
  
}

const func = () => {
  return 1
}

# unknown

// ---- 斷言 as unknown ----
// call API 時有些資料無法推導出來

type Data = {
  userId: number,
  id: number,
  title: string,
  completed: boolean
}

async function getData() {
  // 因為拿到的東西 typescript 也不知道是什麼
  // 所以我們使用斷言告訴 typescript 說是什麼類型
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const data = await res.json() as Data; // 斷言
}

const data1: Data = {
  userId: 1,
  id: 1,
  title: 'title',
  completed: true
}

type Beta = {
  name: string
}

// 假設 data1 為動態資料可能會增多或減少
// 所以先把 data1 轉換成 unknown 在轉換成想要的 type (Beta)
const beta = data1 as unknown as Beta

# class

// ---- class ----

// private 私有
// public 公開
// protected 受保護

class Live {
  roomName: string // 未宣告即為 public
  // 在 typescript 開發的時候不能被訪問
  private id: string
  protected name: string

  // private 跟 protected 差異在於 protected 在繼承時才能使用
  // private 跟 protected 無法在外面使用

  constructor (roomName1: string, id1: string, name1:string){
    console.log('建立直播中...')
    this.roomName = roomName1;
    this.id = id1;
    this.name = name1;
  }

  start() {
    console.log(this.id) 
  }
}

class CarLive extends Live {
  constructor (roomName1: string, id1: string, name1:string){
    super(roomName1, id1, name1)
  }

  start() {
    console.log(super.name)
  }
}

// 外面
const live = new Live('1號', '0001', 'bruce');
console.log(live);

const carLive = new CarLive('CarRoom', '0002', 'bruce2')
carLive.name // 在外面無法訪問到

// 但最後轉譯成 js 時還是都會被看到內容

// js 私有變數寫法
class Live2 {
  // 有 # 代表在 js 內把變數變成私有變數
  #name
  constructor (name: string) {
    this.#name = name
  }
}

const live2 = new Live2('live2')
// 會報錯 不應該從外面讀取它
// console 不出來 #name
console.log(live2.#name)

// 使用 interface 實作 class
interface CarProps {
  name: string,
  age: number,
  start: () => void
}

class Car implenments CarProps {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  start() {}
}

# 泛型

// ---- 泛型 ----
// 在使用時在定義該屬性類型
function print<T> (data: T) {
  console.log('data', data)
}
// 調用 function 時定義該類型
// 可重複使用各種不同類型的 function
print<number>(999)
print<string>('aaa')
print<boolean>(true)

class Print<T> {
  data: T
  constructor(d: T) {
    this.data = d
  }
}

const p = new Print<number>(999)
const p1 = new Print<string>('aaa')
console.log('p', p)
console.log('p1', p1)

typescript utility (opens new window) 裡面有很多工具可以使用

// ---- utility ----
// Record
// 在物件內只能放指定的 key, value
interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
// <CatName, CatInfo> >> key, value
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

const obj1: Record<string, boolean> = {
  name: true,
  age: false
}


// Pick
// 重複使用 interface 裡面宣告的屬性或方法
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
// 拿出 Todo 裡面的 title, completed 來成為新的 type
type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

// Omit
// 移除 interface 裡面宣告的屬性或方法
interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
// 把 Todo description 屬性拿掉
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};