3. 優美地定義 React 型別


由於許多人應該是沒有寫過 TypeScript
於是這邊會簡單介紹 TypeScript 的基礎
如果想知道更深可以參照官網

在剛開始學習 TypeScript 的過程中
一定會想說從官方的範例開始照著實作
後來發現這是夢魘的開始啊~~~
TypeScript 的官方範例非常的完(艱)整(澀)
所以比起啃那些文件,多數人應該是像我一樣 Stack Overflow 學習法

副檔名

TypeScript 有兩種檔案:.ts.d.ts
.ts 副檔名的檔案就是 TypeScript 檔案(廢話)
.d.ts 副檔名則是代表型別的定義檔案
一般來說,我們會將自定義的型別放到 .d.ts 裡面

除了這兩種檔案之外,開發 React 時,會習慣使用 .tsx 來撰寫
如同 .jsx 一般,讓開發時可以更清楚了解這個檔案是 React 的元件
或許有些開發者會覺得不需要區分 .js / .jsx
但對於開發上的溝通來說,還是建議標示比較合適

型別定義

我們之所以喜歡 TypeScript,在於它可以良好地定義我們想要的型別:

type User = {
    name: string
    age: number
    picture: string | null
    role: "admin" | "member" | "guest"
    description?: string
}

上面的自訂型別說明了:

  • 有一個名為 User 的型別
  • User 型別裡面有 name, age, picture, description 屬性
  • picture 可為 string 或是 null
  • role 只可為 "admin", "member" 或是 "guest"
  • description 可以沒有(undefined)

於是我們就可以像這樣子使用:

const addUser = (user: User) => {
    console.log(user.name)
    console.log(user.picture) // 可能為 null
    console.log(user.role) // 只可能為 admin, member, guest
    console.log(user.description) // 可能為 undefined
    console.log(user.foo) // 會編譯失敗
}

有了上面的自訂型別後,我們就可以避免惱人的 TypeError: Cannot call method 'xxx' of undefined,更嚴謹與優美地撰寫我們的程式

這邊刻意不提到 interface,建議直接使用 type 來定義自訂型別即可(用更 functional 思維)
可參考:interface v.s. type

至於 enum 的話,在一般情況我們是不需要使用的,直接使用字串即可,例如:

type Currency = "NTD" | "USD"

只有在一種情形下建議使用 enum,那就是我們需要使用 reverse mapping
也就是有 key 跟 value,且需要從 value 對應回 key 的情況,舉例來說:

enum Reward {
    Poor = 1,
    Soso = 3,
    Good = 5,
}
Reward.Good -> 5
Reward[3] -> "Soso"

不過還是建議減少 reverse mapping 的用法,主要是會讓程式碼的可讀性下降不少
到目前為止,許多套件還是以 type 使用為主

React 中實際使用

相信大家最卡關的應該就是這個部分
使用在 React 裡面時到底該如何定義型別?
我們這邊以各情況來進行說明

定義 Functional Component

type ButtonProps = {
    text: string
}
const Button: React.FC<ButtonProps> = ({ text }) => {
    return <button>{text}</button>
}
export default Button

首先需要先定義 Props,再來定義元件
元件請使用 React 定義好的 FunctionalComponent 型別(簡寫 FC)
在 TypeScript 中的 <> 相當於 function 中的 ()
我們稱為型別參數,可以想成是 function 中的參數
所以 React.FC<ButtonProps> 代表定義一個 Props 為 ButtonProps 的 FC

定義 Event Handler

const App = () => {
    const handleClick: React.MouseEventHandler<HTMLButtonElement> = e => {
        console.log(e)
    }
    return <button onClick={handleClick}>Click</button>
}

在 TypeScript in React 中最難的就是 EventHandler 的定義
這邊拆成兩部分解釋:

  1. Event Handler
    我們必須先選擇這是要處理哪種 Event 的 Handler,Event 種類有很多,都是以 Event 結尾,像是 MouseEvent、FormEvent、KeyboardEvent 等等,可參考 React 官方文件
  2. Target Element
    接下來我們必須定義 Handler 的作用目標是什麼,以範例來說,我們要處理的是作用在「按鈕」上面的點擊事件,所以我們必需要使用 HTMLButtonElement 來宣告我們的作用目標,作用目標有很多種,都是 HTML 開頭,Element 結尾,像是 HTMLFormElement, HTMLDivElement 等等,可參考 MDN

這邊建議使用

const handler: EventHandler = e => {}

而不是

const handler = (e: Event) => {}

主要是因為我們盡量使用 React 已經幫我們定義好的型別,避免自己重複定義 Handler 型別

定義共用型別

type UserRole = "admin" | "member" | "guest"

像這種型別常常會跨元件共用,這邊建議放到 src/types 底下
並且依照功能區分,例如:
跟使用者相關的放到 src/types/user.d.ts
跟付款相關的放到 src/types/payment.d.ts
這邊記得要使用 .d.ts 作為副檔名,這樣在編譯時才會自動將型別放進去

PS. create-react-app v3 是使用 TypeScript 3.7,有提供 optional chaining 的語法,具體用法如下:

// TypeScript 3.7 以前
foo && foo.bar && foo.bar.baz
// TypeScript 3.7 以後
foo?.bar?.baz

結語

型別定義的部分其實有更多部分可以講
但是如果全部打完的話,這篇大概就是我的最後一篇了
所以之後會以實際案例來介紹,希望這篇能稍微幫上點忙

murmur...
還有四天,快過半了!!!








你可能感興趣的文章

.Net MVC authorization Controller and Workcontext extension in razor view

.Net MVC authorization Controller and Workcontext extension in razor view

心情廢文 8/11

心情廢文 8/11

寫作松閱讀組Day00~人性較量

寫作松閱讀組Day00~人性較量






留言討論