上一篇講到:
不要急著重做 Component,也許只是還沒看懂它原本提供的參數。
但今天剛好要講另一面。
有時候我們已經確認:
這個 Component 有很多參數
也真的可以繼續硬改
甚至最後可能做得到
但做著做著會開始出現另一個問題:
為了讓它符合需求,我是不是已經寫太多「對抗元件本身設計」的邏輯了?
這時候真正該問的,可能已經不是:
「還能不能做?」
而是:
「還值不值得繼續這樣做?」
假設需求是:
開始日期
+
結束日期
最直覺可能會想到:
<DateRangePicker />
因為它本來就是拿來選一段日期範圍。
使用者通常可以:
選 Start Date
↓
選 End Date
↓
中間顯示一段 Range
這個 Component 很適合:
一般的日期範圍選擇
但需求如果開始加入一些特殊規則:
End Date 可以是 Indefinite
某些情況只允許改 Start Date
某些情況只允許改 End Date
兩個日期可能有不同 disabled 規則
其中一個欄位可能沒有日期
事情就開始不一樣了。
RangePicker 的核心概念通常是:
Start
↓
End
↓
形成一個 Range
也就是:
這兩個日期本來就是「一組」。
所以元件本身會幫我們處理很多事情:
開始日期
結束日期
範圍預覽
disabled date
panel interaction
range highlight
這些原本都是優點。
但如果需求開始變成:
Start 跟 End 要有不同生命週期
那原本「綁在一起」的優點,
反而可能變成限制。
例如想做到:
Start 可以選
End 不可以選
可能開始找:
disabled
disabledDate
onCalendarChange
onChange
open
value
然後加:
條件 A
條件 B
條件 C
第一個情境修好了。
但測第二個流程:
又壞了。
再補:
條件 D
然後第三個操作方式:
又出現新的問題。
最後很容易變成:
需求本身其實不複雜
但為了維持 RangePicker
↓
程式越來越複雜
這是我後來覺得很重要的一句話。
有時候不是:
我還不夠會用這個 Component。
而是:
這個 Component 的設計模型,本來就和現在的需求不完全一致。
例如需求實際上比較像:
Start Date
→ 一個獨立欄位
End Date
→ 另一個獨立欄位
Indefinite
→ 控制 End Date 是否存在
那真正的資料模型其實是:
Start Date
+
End Date
+
Indefinite
而不一定是:
一個 Range
例如:
<DatePicker
name="startDate"
/>
<DatePicker
name="endDate"
/>
概念變成:
Start Date
↓
自己控制
End Date
↓
自己控制
需要限制:
End 不能早於 Start
就針對 End Date 寫。
需要:
Indefinite = true
就直接:
停用 / 清除 End Date
資料責任變得比較清楚。
完全不是。
如果需求就是:
請選 2026/01/01 ~ 2026/01/31
RangePicker 反而非常合理。
因為:
UI 模型
=
資料模型
=
使用者心智模型
都是:
一段日期範圍
真正要判斷的是:
現在的需求還是不是一個真正的 Range?
我現在會特別注意這些跡象:
為了某個需求一直 override 預設行為
很多 if 都在處理元件特殊狀態
需要阻止原本 Component 很自然的互動
修一個流程又破另一個流程
測試情境一直增加
程式開始比需求本身還難理解
這時候我會停一下。
問:
我是不是正在跟 Component 打架?
例如:
方案 A
繼續使用 RangePicker
技術上可能:
可以做到
但需要:
大量 disabled 判斷
額外 state
控制 panel
處理 preview
處理特殊選取順序
大量 edge case
另一個方案:
方案 B
拆成兩個 DatePicker
可能只需要:
Start Date 規則
End Date 規則
Indefinite 規則
兩個方案都能完成需求。
但:
完成需求
不是唯一評估標準。
還要考慮:
複雜度
可讀性
測試成本
未來維護
使用者體驗
跟既有頁面的一致性
以前選方案可能只看:
哪個最快做出來?
但現在會多看:
今天寫完容易嗎?
三個月後還看得懂嗎?
需求再改一次會不會爆炸?
下一個工程師敢不敢碰?
要測多少種操作流程?
這些其實都是成本。
只是它們不一定會直接出現在工時表裡。
這點我以前比較少想到。
當選:
RangePicker
不只是拿了一個外觀。
也等於接受:
它的互動方式
它的 State 模型
它的事件設計
它對 Start / End 的關係假設
同樣地:
Select
AutoComplete
DatePicker
Table
Form
每個 Component 都不只是長相。
它背後都有一套:
它認為這個問題應該怎麼被操作。
例如:
RangePicker
比較接近:
[startDate, endDate]
也就是:
兩個值組成一個範圍。
而兩個獨立 DatePicker:
startDate
endDate
比較接近:
兩個互相關聯,但仍然獨立的欄位。
如果需求還有:
indefinite = true
那第二種模型可能更自然:
startDate
endDate
indefinite
而不是硬把:
indefinite
塞進 RangePicker 的原始互動模型裡。
做 UI 時,我們會問:
使用者到底在完成什麼任務?
到了前端,
其實也可以拿同一個問題判斷 Component。
例如:
使用者是在「選一段 Range」?
還是:
使用者是在「設定開始日期,並決定是否有結束日期」?
兩句話很像。
但它們其實代表不同的互動模型。
如果第二種才是真正需求,
那拆成:
Start Date
End Date
Indefinite
可能比 RangePicker 更符合使用者心智模型。
有時候已經投入一段時間,
會很容易產生:
我都做到這裡了,再補一個判斷應該就好了。
然後:
再補一個
↓
再補一個
↓
再補一個
最後變成一個很難維護的區塊。
這其實有點像:
沉沒成本
已經花掉的時間,
不應該成為:
繼續使用錯誤方案的唯一理由。
如果重新評估後發現:
換模型比較簡單
那重整方向不代表前面白做。
因為前面的嘗試至少讓我們確認:
這個 Component 的限制在哪裡。
另一個極端也不好。
如果:
只是還沒讀文件
只是少一個 Prop
只是某個 API 不熟
就直接換掉,
又會回到上一篇的問題:
沒有理解現有工具,就重新發明。
所以我現在會用兩階段判斷。
看文件
↓
看 Props
↓
搜尋 Existing Pattern
↓
確認 Component 原本支援什麼
如果有簡單設定可以完成:
沿用。
如果已經確認:
不是少一個 Prop
而是:
需求本身和 Component 的互動模型衝突
那才開始比較:
繼續客製
vs
換更適合的 Component
例如:
需求:
Start Date
End Date
Indefinite
優點
→ 原本就是日期區間
→ UI 緊湊
→ 一般 Range 操作方便
成本
→ Start / End 綁得比較緊
→ 特殊 disabled 流程較複雜
→ Indefinite 需要額外處理
優點
→ Start / End 分開控制
→ 驗證規則比較直覺
→ Indefinite 比較好處理
成本
→ 要自己維護兩者關係
→ 例如 End 不能早於 Start
→ UI 需要自己安排
這樣討論就不再是:
哪個比較厲害?
而是:
哪個的成本比較符合現在的需求?
很多問題其實沒有:
唯一正確答案
而是:
方案 A
有 A 的優缺點
方案 B
有 B 的優缺點
最後依照:
需求
維護成本
風險
一致性
時間
做選擇。
這就是:
Trade-off
取捨。
看到需求,
我會開始問三層:
第一層
能不能做?
第二層
用現在這個 Component 做,複雜度是多少?
第三層
有沒有更符合需求模型的做法?
這三個問題,
比單純:
找一段能跑的 Code
更接近真實開發。
上一篇:
不要太快重做,先確認原本工具能不能做到。
今天則是另一半:
確認過之後,也不要因為「技術上做得到」,就硬把原本 Component 留到底。
因為好的方案不只是:
現在能跑
還應該考慮:
未來好不好懂
好不好測
好不好改
是不是符合真正的資料模型
所以我現在遇到元件問題,
會先問:
我是在使用這個 Component,
還是在對抗這個 Component?
如果越寫越像後者,
那可能就是重新思考工具選擇的時候了。
下一篇可以進到另一個非常容易讓人第一次看到時滿頭問號的工程問題:
Route 為什麼有順序?/:id 怎麼可能把別的網址吃掉?