先前的文章
- [TypeScript] 將TypeScript 導入到React專案 — 起手式
- [TypeScript] 將TypeScript 導入到React專案的常見問題- createRoot參數錯誤
如何設定頁面Props的型別與預設值
一般沒有加入TypeScript 的React component大概會長這樣
function MyComponent({
id,
name='',
amount=0,
...props,
}) {
return (
<div>
....
</div>
)
}
export default MyComponent
這個範例是一個React component 名稱叫做MyComponent
, 這個component 有特別指定幾個props: id
, name
, amount
以及繼承從父元素傳遞到這個元件的所有attributes
也就是props
如何設定型別
TypeScript 設定型別的方式有兩種: interface
與 type
這兩種方式使用的情況大同小異, 如果改寫上面的例子的話
interface
的用法
interface Props {
id: string,
name?: string,
amount?: number,
}
function MyComponent({
id,
name='',
amount=0,
...props,
}: Props) {
return (
<div>
....
</div>
)
}
export default MyComponent
type
的用法
type Props = {
id: string,
name?: string,
amount?: number,
}
function MyComponent({
id,
name='',
amount=0,
...props,
}: Props) {
return (
<div>
....
</div>
)
}
export default MyComponent
那該怎麼選擇?
在React TypeScript Cheatsheet 中 有給出了一套經驗法則
Here’s a helpful rule of thumb:
always use
interface
for public API's definition when authoring a library or 3rd party ambient type definitions, as this allows a consumer to extend them via declaration merging if some definitions are missing.consider using
type
for your React Component Props and State, for consistency and because it is more constrained.
另外, 文件內也提供其他人對type與interface 的行為做比較
主要選用type
當作React component props 的型別定義方式其原因是:
- type比較簡短
- 統一使用
type
避免與interface
混用 - 通常 React component 的Props/State, 在建立component 時 就會定義好有哪些prop 與state, 所以基本上也不太有機會使用到
interface
的 declaration merging . 既然不會用到只有interface
才有的功能, 那選用type
或許是更好的選擇
如果是針對第三方套件, 使用interface 會是更好的選擇
以下透過Chat-GPT 解釋與提供的例子
當我們為不是使用 TypeScript 撰寫的庫(library)編寫第三方環境類型定義時,通過介面進行聲明合併(Declaration merging)非常重要,這樣使用者就可以擴展它們,以填補缺失的定義。
在 TypeScript 中,聲明合併是指將多個介面或類型定義合併為單個定義的過程。這種技術在定義第三方環境類型定義時非常有用,因為它可以使使用者將已有的類型定義和自己的類型定義合併在一起,以滿足自己的需求。
例如,假設有一個名為 moment.js
的 JavaScript 库,我們想要為它撰寫 TypeScript 的類型定義。我們可以創建一個介面,將其命名為 Moment
,並描述 moment.js
返回的日期時間對象的屬性、方法等細節。然後,我們可以使用 declare module
聲明來將我們的類型定義導出,以便其他 TypeScript 程式可以使用它:
interface Moment {
format(formatString: string): string;
add(amount: number, unit: string): Moment;
// ... 其他屬性和方法
}
declare module 'moment' {
export = Moment;
}
然而,如果我們的類型定義不完整,或者使用者希望擴展它以滿足自己的需求,該怎麼辦呢?這時就需要使用聲明合併來解決這個問題。使用者可以創建一個新的介面,並將其與我們的介面合併,以添加缺失的屬性、方法等。例如:
// 將 Moment 和自定義定義合併
interface MyMoment extends Moment {
toMyString(): string;
}
declare module 'moment' {
// 將 MyMoment 和 Moment 合併
interface Moment extends MyMoment {}
}
通過這種方式,使用者可以擴展我們的類型定義,以滿足自己的需求。這種方式可以提高庫的可擴展性和彈性,也使得 TypeScript 使用者能夠更加方便地使用庫。
這樣在使用type
與interface
就有更清楚的使用時機
如何給定預設值
最直接的方式, 可以像上面例子的寫法, 在解構component 的props的同時, 也給予非必要
的prop 一個預設值, 更詳細的用法可以參考MDN的Default parameters
從上面的範例可以看到, component的prop只有id 沒有給預設值, 因為id
在這個元件當中被視為必要的
prop, 也就是使用這個component時, 就一定要傳入id這個prop. 如果沒有, 很有可能會發生錯誤, 或是出現不預期的結果
其他的props, 如: name
, amount
都有給預設值, 所以就算不傳入這兩個prop, 也因為有預設值, 所以發生錯誤的機率就會降低很多