今天我們將繼續優化 Todo
列表,昨天我們將其內容放在了 App.tsx
中:
import './App.css'
import Header from './components/Header'
import logo from './assets/logo.png'
import { useState } from 'react'
import TodoList from './components/TodoList'
export type TodoItem = {
id: number
title: string
isFinished: boolean
}
function App() {
const [todos, setTodos] = useState<TodoItem[]>([])
const createTodoHandler = () => {
const newTodo: TodoItem = {
id: Math.random(),
title: 'Learn JavaScript',
isFinished: false,
}
setTodos((prevTodos) => [...prevTodos, newTodo])
}
return (
<main className='w-[500px] h-[100dvh] portrait:w-[90%] flex flex-col'>
<Header image={{ src: logo, alt: 'logo' }}>
<h1>Todo List</h1>
</Header>
<div className='w-full mb-[20px] h-fit flex'>
<label htmlFor='newTodo'></label>
<input
type='text'
id='newTodo'
name='newTodo'
className='flex-1 mr-[16px] px-3 rounded-[5px] focus:outline-none'
/>
<button onClick={createTodoHandler}>Create</button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id} className='list-none'>
<Todo isFinished={todo.isFinished}>
<p>{todo.title}</p>
</Todo>
</li>
))}
</ul>
</main>
)
}
export default App
現在,我們要將這段列出 Todo
的程式碼獨立出來,提取成一個專門的元件,讓程式碼更加結構化:
<ul>
{todos.map((todo) => (
<li key={todo.id} className='list-none'>
<Todo isFinished={todo.isFinished}>
<p>{todo.title}</p>
</Todo>
</li>
))}
</ul>
首先,在 components
資料夾建立 TodoList.tsx
,並將上方的程式碼貼入:
export default function TodoList() {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} className='list-none'>
<Todo isFinished={todo.isFinished}>
<p>{todo.title}</p>
</Todo>
</li>
))}
</ul>
)
}
接下來,我們要為 TodoList
的 props
定義型別,以確保 todos
的資料結構一致,從而提高型別安全性:
import Todo from './Todo'
type TodoListProps = {
todos: {
id: number
title: string
isFinished: boolean
}[]
}
export default function TodoList({ todos }: TodoListProps) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} className='list-none'>
<Todo isFinished={todo.isFinished}>
<p>{todo.title}</p>
</Todo>
</li>
))}
</ul>
)
}
在 App.tsx
中引入 TodoList
元件,並傳入 todos
:
return (
<main className='w-[500px] h-[100dvh] portrait:w-[90%] flex flex-col'>
<Header image={{ src: logo, alt: 'logo' }}>
<h1>Todo List</h1>
</Header>
<div className='w-full mb-[20px] h-fit flex'>
<label htmlFor='newTodo'></label>
<input
type='text'
id='newTodo'
name='newTodo'
className='flex-1 mr-[16px] px-3 rounded-[5px] focus:outline-none'
/>
<button onClick={createTodoHandler}>Create</button>
</div>
<TodoList todos={todos} />
</main>
)
雖然目前運作正常,但我們會發現 App.tsx
與 TodoList.tsx
中的型別定義是重複的。為了避免重複定義型別,我們可以將 TodoItem
的型別匯出並共享。首先,將 TodoItem
型別從 App.tsx
匯出:
export type TodoItem = {
id: number
title: string
isFinished: boolean
}
接著,到 TodoList.tsx
中匯入這個型別,並替換掉重複的部分:
import Todo from './Todo'
import { type TodoItem } from '../App'
type TodoListProps = {
todos: TodoItem[]
}
export default function TodoList({ todos }: TodoListProps) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} className='list-none'>
<Todo isFinished={todo.isFinished}>
<p>{todo.title}</p>
</Todo>
</li>
))}
</ul>
)
}
這樣的修改不僅減少了重複代碼,還提高了型別的可重用性和一致性,讓程式碼在維護和擴展時更加便捷。
在上面的範例中,我們匯出了使用 type
方法定義的型別:
export type TodoItem = {
id: number
title: string
isFinished: boolean
}
這樣的寫法,在 interface
也是完全成立的:
export interface TodoItem {
id: number
title: string
isFinished: boolean
}
在大型專案中,匯出型別能夠有效提升程式碼的維護性。當我們將型別獨立出來並匯出後,不僅可以在多個元件間共享相同的型別定義,還能確保型別的一致性。這樣的做法可以避免重複編寫相同的型別邏輯,減少潛在的錯誤風險。同時,如果日後有需求修改型別結構,只需在一個地方進行調整,所有使用該型別的地方都會自動更新,讓開發過程更加順暢和高效。