目前我們的 Todo
元件是放在 App.tsx
,因此先到 App.tsx
放入我們要傳遞的 props
:
import './App.css'
import Todo from './components/Todo'
function App() {
return (
<main>
<Todo content='Learn typeScript' isFinished={false} />
</main>
)
}
export default App
接著我們回到 Todo.tsx
使用解構的方式獲取 props
,並將原本寫死的 Content
以及 isFinished
換成傳入的 props
。
注意!傳入的 props 是一個物件,這是 React 的機制,而非 TypeScript 的特殊語法:
export default function Todo({ content, isFinished }) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
<p>{content}</p>
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
到目前為止都與我們原先開發 React 方式相同,唯一不同的部分,就是我們需要為傳入的 props
定義型別,經過定義型別後,你會發現 IDE 不再報錯:
export default function Todo({
content,
isFinished,
}: {
content: string
isFinished: boolean
}) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
<p>{content}</p>
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
雖然我們上面的做法並不會有問題,但是當傳入的 props
內容變多時,將會影響程式碼的閱讀性。
還記得我們在 Day07 的時候介紹的 Custom Types & Interface
嗎?
我們可以將先前學到的型別定義方法應用到 React 中。
使用 type
將型別提取出來,再將該型別放入 props:
type TodoProps = { content: string; isFinished: boolean }
export default function Todo({ content, isFinished }: TodoProps) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
<p>{content}</p>
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
使用 Interface
也是完全沒有問題的,這邊需要注意的是,如同我們先前提過的,使用 Interface
定義型別時,不需像使用 Custom Types
那樣在中間放入等號:
interface TodoProps {
content: string
isFinished: boolean
}
export default function Todo({ content, isFinished }: TodoProps) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
<p>{content}</p>
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
相信 React 開發者們對於 children
這個 props
肯定不陌生,當元件包覆了其他內容,這個內容就會是該元件的 children
,首先,我們先到 App.tsx
把 content
改為 children
的方式傳入:
import './App.css'
import Todo from './components/Todo'
function App() {
return (
<main>
<Todo isFinished={false}>
<p>Learn typeScript</p>
</Todo>
</main>
)
}
export default App
接著我們回到 Todo.tsx
為 children
定義型別,你認為 children
的型別是什麼呢?因為我們的內容是 Learn typeScript
,所以是 string
嗎?
我們都知道在 React 元件中 return
的內容會是 JSX
,所以我們不能直接定義為 string
。
在這邊要提到一個特別的型別,它叫做 ReactNode
,ReactNode
是 React 的型別定義之一,已經隨 React 工具包安裝在專案內,它的用途是用來表示任何可以作為 React 元件子元素的內容,而不僅限於字串,還包括數字、元素、陣列等
打開 package.json
,你會在 devDependencies
裡看到以下這兩個工具,這些工具使我們可以處理一些 React 中的特別型別:
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
若我們要將原本的 content
改為使用 children
引入,我們需要使用 ReactNode
定義型別,並將原本的 content
內容改為 children
,在 App.tsx
中,我們已經為 children
加上了 <p>
tag,因此在這邊我們可以直接用 {children}
取代原本的 <p>{content}</p>
:
import { type ReactNode } from 'react'
interface TodoProps {
isFinished: boolean
children: ReactNode
}
export default function Todo({ isFinished, children }: TodoProps) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
{children}
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
為了不需要每次都手動為 children
指定型別,React 提供了 PropsWithChildren
泛型工具,它預設將 children
視為 ReactNode
:
import { type PropsWithChildren } from 'react'
// interface TodoProps {
// isFinished: boolean
// }
type TodoProps = PropsWithChildren
export default function Todo({ isFinished, children }: TodoProps) {
return (
<div className='flex items-center gap-[20px] justify-between mb-3'>
<input type='checkbox' checked={isFinished} />
{children}
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
把滑鼠移到 {children}
上面,你會看到 IDE 提示 (parameter) children: React.ReactNode
,這與我們先前指定的型別是一致的:
程式碼報錯的原因是因為我們尚未為 isFinished
指定型別。將滑鼠移到 PropsWithChildren
上時,IDE 會提示我們:PropsWithChildren
是一個泛型工具,允許我們傳遞其他型別作為參數。接下來,我們可以透過泛型來為 isFinished
定義型別:
將 isFinished
的型別放入 PropsWithChildren
的泛型參數中即可解決:
import { type PropsWithChildren } from 'react'
// interface TodoProps {
// isFinished: boolean
// }
type TodoProps = PropsWithChildren<{ isFinished: boolean }>
export default function Todo({ isFinished, children }: TodoProps) {
return (
<div className='flex items-center gap-[20px]'>
<input type='checkbox' checked={isFinished} />
{children}
<div className='flex gap-[16px]'>
<button>Edit</button>
<button>Delete</button>
</div>
</div>
)
}
在 import
中加上 type
是 TypeScript 3.8 版本新增的功能。讓我們看一個簡單的例子:
import { Example } from "./example.ts";
此時匯入的 Example
是一個值還是型別呢?當我們明確指定 type
時,可以提高程式碼的可讀性,並有助於加快編譯速度,因為它僅引入型別資訊而不會包含任何運行時的值。在某些情況下,省略 type
可能不會造成問題,但如果使用其他工具進行編譯,省略 type
可能會導致錯誤。
如果想了解更多,可以參考官方網站版本文件,或是 import 和 import type的區別。
在 React 中,使用 map
渲染列表時,React 需要每個項目都有唯一的 key
,key
主要是用來在渲染時區分不同的元素,這樣可以更直觀地幫助 React 做出更新。雖然我們在元件中傳入了 key props
,但不需要為 key
指定型別,因為 key
是 React 的內建屬性,TypeScript 會自動處理它的型別:
interface FruitProps {
name: string
}
function Fruit({ name }: FruitProps) {
return <li>{name}</li>
}
function App() {
const fruits = [{ name: 'Apple' }, { name: 'Orange' }, { name: 'Banana' }]
return (
<ul>
{fruits.map((fruit) => (
<Fruit key={fruit.name} name={fruit.name} />
))}
</ul>
)
}
export default App