iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

一個畫面變成多個檔案

上一篇將漫畫與動畫收藏清單的需求交給 AI 後,它沒有把所有程式碼都寫在 App.tsx,而是拆成了好幾個 Component:

App
├─ PageHeader
├─ AddItemForm
└─ CollectionList
   └─ CollectionItem

一開始可能會覺得說畫面明明沒有多複雜,真的需要拆成這麼多檔案嗎?
如果全部寫在 App.tsx,應該也能做出一樣的東西吧?既然自己看不太懂 AI 拆分的邏輯,那就是先停止瞎猜,直接問它:

為什麼要拆成 PageHeaderAddItemFormCollectionListCollectionItem?請分別說明拆分的原因。哪些 Component 適合獨立?哪些其實也可以留在 App.tsx?先不要修改程式碼。

這次先讓 AI 講理由,而不是看到它拆好就直接接受。

App 負責管理整個收藏清單

從 AI 給的回答來看,App 就是整個頁面的管理者。

目前收藏資料的 State 放在 App.tsx

const [items, setItems] = useState<CollectionEntry[]>(loadItems)

新增、修改狀態和刪除作品的函式也都放在這裡,再透過 Props 交給其他 Component:

<PageHeader totalCount={items.length} />

<AddItemForm onAdd={addItem} />

<CollectionList
  items={items}
  onStatusChange={updateStatus}
  onDelete={deleteItem}
/>

如果連頁面標題、完整表單和每一筆收藏的 JSX 全部塞進來,App.tsx 就會長成一大串。
拆開之後至少可以先從這幾行看出頁面大概長怎樣,App 就專心處理整個專案共用的收藏資料就好,不用什麼都攬在自己身上。

AddItemForm 負責新增作品

AddItemForm 包含作品名稱、類型與觀看狀態,也有自己的 State:

const [title, setTitle] = useState('')
const [type, setType] = useState<MediaType>('manga')
const [status, setStatus] = useState<WatchStatus>('want')

送出表單後,它會透過 onAdd 把整理好的資料丟給 App
這個 Component 不是只有一小段畫面而已,還包著輸入資料跟處理表單的整套流程。看到 AddItemForm 這個名字,就知道要改新增功能該往哪個檔案找,不用大海撈針。

清單為什麼還要拆成兩層?

收藏清單又被拆成:

CollectionList
└─ CollectionItem

CollectionList 負責接收整個 items 陣列,判斷目前是否有收藏資料,再用 map() 產生每一個 CollectionItem

{items.map((item) => (
  <CollectionItem
    key={item.id}
    item={item}
    onStatusChange={(status) =>
      onStatusChange(item.id, status)
    }
    onDelete={() => onDelete(item.id)}
  />
))}

CollectionItem 則負責顯示單筆作品,包括作品類型、名稱、觀看狀態跟刪除按鈕。

這個拆法跟之前 Todo List 裡的 TodoListTodoItem 可以說是同一套:

  • CollectionList:管整份清單
  • CollectionItem:管其中一筆資料

每一筆作品都會重複用到相同的畫面結構,所以把單筆作品獨立出來確實比較好理解,用比喻來說就是一個大型布告欄裡,每新增一個東西就會出現一張卡片。

PageHeader 也一定要拆嗎?

PageHeader 只負責顯示頁面標題和收藏數量:

<PageHeader totalCount={items.length} />

跟表單、收藏清單比起來,它現在的內容單純很多,也沒有自己的 State。

AI 的判斷是,拆出來可以讓 App 的頁面結構更清楚,但以目前只是用一個練習用的小型專案規模來看,其實也能直接把 Header 的 JSX 留在 App.tsx 裡。
所以 PageHeader 感覺不是非拆不可,比較像是一種整理習慣,拆不拆就看需求而已。
以結論來說,Component 怎麼拆沒有標準答案,有些部分很明顯適合獨立,有些就真的要看專案大小跟自己看得順不順眼了。

Component 是拆得越小越好嗎?

當然也不是每一小段 JSX 都要獨立成 Component。

例如,如果再把 CollectionItem 繼續拆成:

TypeBadge
ItemTitle
StatusSelect
DeleteButton

這些東西目前內容不多,也只有一個地方在用,拆開後反而要一直切換檔案找東西,還可能多出一堆 Props,不見得比較好懂,感覺反而更麻煩。

目前我先從幾個方向判斷要不要拆:

  • 這一部分有沒有明確的用途?
  • 是否有自己的 State 或操作流程?
  • 是否會在畫面中重複出現?
  • 留在原本的 Component 裡,會不會讓內容變得太長?
  • 拆開後,是不是更容易找到需要修改的程式碼?

Component 不一定要重複使用才值得拆分,像 AddItemForm 雖然目前只出現一次,但因為它有完整的表單功能,獨立出來讀起來算是輕鬆不少。

React 官方文件也提到,一個 Component 理想上可以先專注做一件事,等它逐漸變大時,再繼續拆成比較小的 Component。
React 官方文件:React 的思考模式

最後要接受 AI 的拆分嗎?

以練習來說,當然可以看個人決定。你可以直接全部接受,也可以看過、理解程式內容後,再決定哪些部分需要更改。最重要的是確認自己真的有理解程式碼在寫什麼,也要知道過程中 AI 到底幫你改了什麼、刪了什麼。

針對第一次生成的內容,我自己大概的判斷是:

  • AddItemForm:有自己的 State 與表單流程,保留
  • CollectionList:負責處理整份陣列與空白狀態,保留
  • CollectionItem:每筆作品會重複使用,保留
  • PageHeader:不一定需要獨立,但拆開也不會造成什麼負擔

所以這次先不動 AI 原本的 Component 結構。

不修改不是因為 AI 一定是對的,而是確認它的理由、再自己看過實際程式碼之後,覺得目前這樣拆還算合理,沒必要為改而改。
這次協作也沒生出什麼新程式碼,單純是我對看不懂的結構提出問題,讓 AI 說明設計理由,再自己判斷要不要買單。

今日回顧

  • App 負責管理主要的收藏資料與操作函式
  • AddItemForm 負責新增作品的表單流程
  • CollectionList 負責整份清單,CollectionItem 負責單筆作品
  • PageHeader 拆不拆沒有標準答案
  • Component 不一定要重複使用才值得拆分
  • Component 不是拆得越小越好
  • AI 拆好的結構還是要先搞懂理由,再決定要不要接受

先這樣,下篇見!


上一篇
Day 20|開始做專案,程式碼交給 AI
下一篇
Day 22|Props、State 與資料流:重新看懂資料怎麼流動
系列文
程式碼 AI 寫,我負責看懂:30 天拆解 React × TypeScript22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言