【TypeScript學習系列】學習如何自定義型態及設置各種型態的方法,打造簡潔好整理的程式碼

接下來是一些型態延伸性的用法,及function凾式宣告的用法。因為本來JavaScript的型態觀念就比較沒這麼強,所以在型態的學習上就會稍微困難一點。Function在使用上也比以往更嚴謹,一開始用會有點綁手綁腳,忘記設定型態,但習慣之後,會是協助開發除錯很好的功能

自定義型態(type)及interface

Type

主要在宣告的時候,想要多種形態,簡單可以使用union,但如果同時很多型態,看起來就會非常混亂像,而且同時很多變數都要相同型態,就會像這樣

let testName1:(string| number| boolean|null|undefined)[] =[]
let testName2:(string| number| boolean|null|undefined)[] =[]
let testName3:(string| number| boolean|null|undefined)[] =[]

為了統整起來,所以就有了自定義型態type出現了,所以我們只需要先宣告好,就可以重複使用,並且很簡潔

type Action = number | string | boolean;

let action1: Action;
let action2: Action;

物件也可以宣告,是先知道物件內的key及相對應的型態,就能先預設好該對應的型態,像是跟後端傳輸的時候,就可以用到了

type OBJ = { name: string; age: number };
let obj: OBJ = {
  name: "",
  age: 0,
};

Interface

這有點類似自定義型態,但還是有些微的不同,後面會做個對比,特色是宣告的時候,沒有等號

interface UserCard {
  name: string;
  desc: string;
}
let userCard: UserCard = {
  name: "",
  desc: "",
};

type跟interface的差別

這邊會稍微提一下,type跟interface的相同跟不同之處

擴充

主要是除了共同的型態之外,還想要額外再增加功能,例如動物型態,用在狗身上,我想增加跑動功能,但鳥跟魚不需要,所以要額外增加

type擴充

type的部分是使用”&”符號來連結要擴充的部分,沒有要擴充的部分,可以直接繼承

type Animal = {
  name: string;
};

type Dog = Animal & {
  age: number;
};

type Cat = Animal;

let dog: Dog = {
  name: "",
  age: 1,
};

let cat: Cat = {
  name: "",
};

Interface擴充

其實跟type擴充基本上沒兩樣,只是interface擴充的方法是使用extends

interface Fish {
  name: string,
}

interface BigFish extends Fish{
  age:number,
}

interface SmallFish extends Fish{}

let bigFish :BigFish =  {
  name: "",
  age: 1,
}

let smallFish :SmallFish =  {
  name: "",
}

合併

再來這個就是只有interface才做得到的功能,具體做法就是將相同名子的在宣告一次,這樣的話他會連同舊的內容合併到新的內容,所以當你實作的時候,就必須將他新舊兩個內容都實做出來

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

let bigFish: Fish = {
  name: "",
  age: 1,
};

那如果強行讓type使用這個功能的話會造成bug,造成重複宣告的錯誤

type Animal  ={ //這行報錯
  name:string
}
type Animal  ={ //這行報錯
  name:string
}

Enums

這是Javascript比較不會去用到的功能,但在前後端通訊的時候是非常實用的功能,我自己本身也有接後端的經驗,常常在分階段的時候,我們會用數字代替

但今天如果全寫數字,我跟後端人員確實都能理解,但一個新加入的人,單看程式碼是不可能理解這在幹嘛的,像是這樣

一段程式碼,雖然都知道不同階段要執行裡面的任務,但這些0~4到底道代表什麼只能去猜,那如果今天有10個階段,不看文件是不是只能通靈了

if(nowStatus ==0){
  reset()
}else if(nowStatus ==1){
  open()
}else if(nowStatus ==2){
  close()
  reset()
}else if(nowStatus ==4){
  win()
}

那使用Enums就能從字面上去了解這些數字代表什麼,也比較好閱讀,甚至不用開文件就能理解

// 0 1 2
// 0 > 壓柱
// 1 > 開牌
// 2 > 結算
enum PokerStatus {
  "bet" = 0,
  "open" = 1,
  "win" = 2,
}

let nowStatus = 0;

if (nowStatus === PokerStatus.bet) {
  //...
}

Function宣告

TypeScript跟JavaScript的差別在於型態,光這點就出現很多的變化,所以先來看看JavaScript是怎麼宣告的

JavaScript宣告function簡潔有力,但存在很多問題,參數未受到規範,所以很多時候傳到錯誤的參數也不知道

function get(a, b) {
  return a + b;
}

那TypeScript,清楚的定義要進來的參數是什麼

※string+number是string合併回傳,這邊額外提醒

function get(name: string, age: number) {
  return name + age;
}

可選參數

但有時候我只有部分參數需要用到,有些參數只是附加的,那就要使用可選參數,上一篇提到可選物件key,這裡也可以使用相同的概念,參數後面加上”?”

【TypeScript學習系列】學習基礎宣告的方法,了解TypeScript與JavaScript的差異

(物件宣告那邊)

//可選參數
function setUser(name: string, age?: string) {
}

但是可選參數的參數要來拿做事情的時候可能報錯,畢竟不是強制加入的參數有可能是空值,延續上個範例

function setUser(name: string, age?: string) {
    return age.split(""); //這行報錯
}

所以要把判斷加進去

function setUser(name: string, age?: string) {
  if (typeof age === "string") {
    return age.split("");
  }
}

加入型態的參數

說實在這個概念我真的也不太懂,但我也曾經使用過JS將物件當參數丟進去,並且該物件為模組的資料結構(struct),大概是相同的概念吧,具體用法是像這樣,先設定好型態,並賦予參數該型態,那麼參數就能使用這些屬性

info用 “ . “就可以使用age這個屬性

type Info = {
  name: string;
  age: number;
};

function getUserInfo(info: Info) {
  info.age;
}

function回傳

既然宣告參數有形態,那function回傳值也要有形態,可以分為兩種,一種就是事先設定的,另一種就是看他回傳什麼就會決定是什麼

function getNum() {
  return 123;
}

function getString(): string {
  return "";
}

function回傳陣列

陣列回傳相對來說也比較複雜一點點,如果不是明確的去設定型態,那可能會傳錯型態回來

乍看之下沒什麼問題,但其實這樣回傳回來,是定義為兩種型態的值回來,所以可能當數字跟文字互換之後,TypeScript還是不會發現錯誤

function getArray() {
  return [0, 1, "hank"];
}

const [id, age, userName] = getArray();

使用斷點

如果要嚴格規範的話,就是使用斷點加進去,這樣就能很明確的知道每個值該回傳什麼樣的型態

function getArray2() {
  return [0, 1, "hank"] as [number, number, string];
}

const [id2, age2, userName2] = getArray2();


Rest進階技巧

這個技巧我之前也有在JS學過,但我完全沒有使用過,不過這是讓程式碼可以更簡潔的功能,也就是他可以將所有的參數蒐集起來成為一個陣列

function addNum(...num: number[]) {}

addNum(1,2,3,5,6,8,9,10);

那反過來也可以解構,就是將陣列解構成為一個一個參數,像是這樣

function printNum(num1: number, num2: number, num3: number) {
  console.log(num1);
  console.log(num2);
  console.log(num3);
}

let numArr = [1, 2, 3] as const;

printNum(...numArr);

不回傳的function

有時候我們使用Function只是要把一些重複性的方法,或是有特別意義的方法集合整理起,並不需要強制回傳值,這時候就可以使用void

function sayHello() {
  console.log("hi");
}

或是報錯時不回傳值,則使用never

function getUserData(): never {
  throw new Error("....");
}

至於這兩個差異呢,目前上網查了一下,主要是再說void代表的是沒有任何型態,never是永遠不存在”值”的型態,所以即便返回空值,都是void型態,永遠不返回且報錯則是never

泛型的Function

我們有可能同時要用多種型態的回傳值及參數,但是只有使用當下才會知道到底要用什麼,可是全部寫成union又太長,這時候就可以使用泛型來解決

泛型的特色就是使用 “<>” 的符號

function webPrint<T>(data: T) {
  console.log(data);
}

webPrint<string>("T");
webPrint<number>(123);
webPrint<boolean>(false);

多載的Function

這個是多種宣告,但只用一個實作來表達的方式,感覺上是一種開發的風格,具體要不要用其實看個人

這就是同一個名子的函式,但具體來說只使用一個實作來完成,雖然把前三行都註解掉就是union的寫法,但多寫上面這段就比較清楚整體的東西有哪些

function getData(data: string): string;
function getData(data: number): number;
function getData(data: boolean): boolean;
function getData(data: string | number | boolean): string | number | boolean {
  return data;
}

getData("");

或是改成unknown

function getData(data: string): string;
function getData(data: number): number;
function getData(data: boolean): boolean;
function getData(data: unknown): unknown {
  return data;
}

getData("");

總結:

今天也是繼續學習布魯斯的 TypeScript 入門攻略|輕鬆打造實時聊天室這堂課,這章節就比較多JavaScript沒有的觀念,像是Type的宣告,這是因為多了型態上的延伸,而interface這種限制的方式,在JavaScript這樣自由度高的語言更是不會碰到,那Enums這概念是因為我工作專案有碰過,所以比較快理解他的概念。function部分則是多了參數及回傳值這兩個型態之後,延伸出來的變化也是非常多,這是JavaScript這樣的弱語言沒碰過的概念。

這些寫法雖然不會跟JavaScript差太多,但如果自己看又沒有其他強語言基礎的話,就會花上不少時間,不過布魯斯這堂課確實解釋的相當詳細,基本上聽過一次就能很快吸收這些觀念,感覺上是他很了解JavaScript這個語言的整個演化過程,所以也很清楚要從哪裡補充,才是對於JavaScript轉TypeScript學習者更好吸收的方式

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。