上一篇講到 State 和 Props。
當所有內容都還寫在同一個 Component 裡時,其實不太會感覺到資料傳遞有多麻煩。
但真正開始拆 Component 之後,問題就會突然變明顯:
這份資料是誰的?
這個 Component 需要哪些資料?
要不要從 Parent 傳進來?
誰負責修改?
而這也是我後來才慢慢發現的一件事:
拆 Component,不只是把程式碼切成很多檔案。
真正要拆的是:
責任
+
資料依賴
剛開始寫 React 時,我對 Component 拆分的理解很單純:
檔案很長
↓
太亂
↓
拆 Component
例如一個 Detail Page:
function DetailPage() {
return (
<>
<Card title="Basic Information">
{/* 很多欄位 */}
</Card>
<Card title="Product Information">
{/* 很多欄位 */}
</Card>
<Table
columns={columns}
dataSource={data}
/>
<Card title="Document Information">
{/* 又很多欄位 */}
</Card>
</>
);
}
隨著功能增加,可能又繼續塞:
DetailPage
├─ API Request
├─ Form
├─ Basic Information
├─ Product Information
├─ Document Information
├─ Permission
├─ Button
├─ Modal
└─ 各種 function
這時候最直覺的感覺就是:
好長,我要拆。
但後來才發現:
行數很多,只是一個訊號,不是判斷 Component 要不要拆的唯一標準。
例如一個頁面裡有:
DetailPage
│
├─ BasicInfo
├─ ProductInfo
├─ DocumentInfo
└─ PerformanceInfo
這些區塊都有一個共同點:
可以很清楚說出它的責任。
例如:
BasicInfo
→ 負責基本資訊
ProductInfo
→ 負責商品相關內容
DocumentInfo
→ 負責文件資訊
如果一塊 UI 已經可以很清楚地被命名,而且有自己相關的資料與畫面邏輯,就可以開始考慮拆成 Component。
例如:
function DetailPage() {
return (
<>
<BasicInfo data={detail} />
<ProductInfo
products={detail.products}
currency={detail.currency}
/>
<DocumentInfo
documents={detail.documents}
/>
</>
);
}
這時候 Parent 的責任會變得比較清楚:
DetailPage
↓
取得 / 管理主要資料
↓
把需要的資料交給各個 Component
而:
ProductInfo
就只需要專心處理:
Product 相關資料
這是我在實際專案裡很有感的一件事。
以前全部塞在同一個檔案時:
API Response
↓
很多變數
↓
很多 function
↓
很多 JSX
我常常看不出:
到底哪一份資料是誰在用?
但開始拆 Component 之後,就會被迫回答:
BasicInfo 需要什麼?
ProductInfo 需要什麼?
DocumentInfo 需要什麼?
例如:
<ProductInfo
products={detail.products}
currency={detail.currency}
/>
光看 Props,就可以知道:
ProductInfo 依賴
products
currency
所以拆 Component 對我來說不只是:
「讓檔案短一點。」
還有另一個很重要的作用:
讓每一塊程式真正依賴哪些資料變得更清楚。
學到 Component 之後,也很容易走到另一個極端。
例如:
<div>
<Title />
<Name />
<Price />
<Button />
</div>
然後全部各自一個檔案:
Title.tsx
Name.tsx
Price.tsx
Button.tsx
結果只是想找一個畫面,卻要一直跳檔案。
所以:
Component 不是拆越多越好。
我現在會先問幾個問題:
① 這一塊有明確的責任嗎?
② 它是不是有自己的一組資料或邏輯?
③ 之後修改這一塊時,
能不能主要只關心這個 Component?
④ 它有沒有可能重複使用?
其中第 ④ 點不是必要條件。
以前我很容易以為:
「只有會重複使用的東西才需要拆 Component。」
但後來才發現,就算只用一次,只要責任夠獨立,拆開仍然可以提升可讀性與維護性。
我曾經遇過一個頁面,功能一路增加之後,很多內容都集中在同一個 Component。
最開始看起來可能還能接受:
Page
├─ 基本資料
├─ 商品資料
└─ 文件資料
但功能持續增加後,慢慢變成:
Page
├─ API Request
├─ Permission
├─ Form
├─ Tab
├─ Table
├─ Modal
├─ Button
├─ 各種資料處理
└─ 各種顯示邏輯
後來開始依照不同畫面與功能責任拆開:
Page
│
├─ Info Section
├─ Product Section
├─ Document Section
└─ Other Section
但真正開始拆後,我才發現:
把 JSX 搬出去其實只是第一步。
接著馬上會遇到:
這個 Component 到底需要哪些資料?
哪些資料 Parent 已經有?
哪些 Props 要傳進去?
誰負責修改?
哪些資料很多 Component 都需要?
這時候才真的接回上一篇講的:
State
↓
Props
↓
Child Component
比起一開始就開始剪程式碼,我現在比較想先整理責任:
DetailPage
│
├─ BasicInfo
│ └─ basicData
│
├─ ProductInfo
│ ├─ products
│ └─ currency
│
└─ DocumentInfo
└─ documents
然後問:
哪些資料只有這個 Component 需要?
↓
留在 Component
哪些資料 Parent 已經有?
↓
透過 Props 傳
哪些資料很多不同 Component 都需要?
↓
可能要再考慮其他共享方式
這時候 Component 拆分就不再只是:
「把程式碼搬到另一個檔案。」
而是:
重新整理畫面責任
↓
整理資料依賴
↓
重新確認資料流
以前:
檔案很長
↓
拆
現在比較會多想一層:
這一塊負責什麼?
↓
它需要哪些資料?
↓
它的邏輯是不是已經相對獨立?
↓
拆出去後,責任會不會更清楚?
所以 Component 拆分對我來說,已經不只是整理畫面。
而是:
把「誰負責什麼」這件事說清楚。
當 Component 一層一層拆下去之後,下一個問題也很快會出現:
Parent
↓ Props
Child
↓ Props
GrandChild
↓ Props
GrandGrandChild
如果中間的 Component 根本不需要這份資料,卻只是負責一直往下傳,就會開始出現另一個問題:
Props 到底要傳幾層?
下一篇就來看:
Props 傳到懷疑人生時,Context 到底是在解決什麼?