![]()
一個Title組件里塞了3種按鈕邏輯,類型用字符串枚舉控制。這種寫法在2023年的代碼評審里依然能過,但產品上線后第4個需求進來時,開發者開始罵娘。
這不是技術債的典型案例,是設計模式課沒講透的后遺癥。開閉原則(OCP,Open Closed Principle)說"對擴展開放,對修改封閉",但很多人直到被需求淹沒才懂這句話的代價。
一個Props對象埋下的雷
原文里的Title組件接收5個Props:title、type、href、buttonText、onClick。type字段用聯合類型鎖死三種形態:"default" | "withLinkButton" | "withNormalButton"。
渲染邏輯用條件分支硬編碼:type匹配字符串,決定要不要渲染按鈕、渲染哪種按鈕。這種寫法在需求穩定時人畜無害,甚至顯得"簡潔"。
問題藏在"修改"兩個字里。
產品經理說標題旁邊要加tooltip,開發者得做三件事:改Props類型定義、加"withTooltip"分支、測試前三種形態有沒有被改壞。每次擴展都要碰核心文件,回歸測試成本線性增長。
更隱蔽的坑是type字段的語義污染。它描述的是"有沒有按鈕、什么按鈕",但新需求可能是"按鈕旁邊加圖標"或"標題帶副標題"。字符串枚舉的擴展性在第三個需求后就捉襟見肘。
組合模式怎么解耦
重構后的方案把Title拆成容器組件和具體實現。基礎Title只負責布局和標題渲染,children作為插槽暴露出去。
Link按鈕版本、普通按鈕版本各自封裝成獨立組件,內部組裝基礎Title。新需求來了?新建一個TitleWithTooltip組件,完全不碰現有代碼。
擴展成本從"改核心+全量回歸"變成"寫新文件+單測新組件"。
這種模式在React生態里有成熟實踐。Ant Design的Modal、Form,Radix UI的Dialog,都是容器+插槽的思路。區別只在于有沒有把"不要改舊代碼"當成鐵律。
原文沒提但值得注意:組合模式在TypeScript里需要處理好children的類型約束。基礎Title的children用ReactNode太寬,用特定組件類型又太死,實際項目中往往要配合render props或context做類型收窄。
為什么團隊明知故犯
第一種寫法在代碼量上確實少。5個Props、3個條件分支,200行內搞定。組合模式需要3個文件、更多樣板代碼,評審時容易被質疑"過度設計"。
但代碼量的比較有個陷阱:它只算第一次寫的成本,不算后面修改的成本。原文的場景里,第二次加tooltip時,第一種方案的總代碼量(含測試)就已經反超。
另一個阻力是團隊習慣。很多前端從jQuery時代過來,習慣了"拿到需求改組件"的肌肉記憶。React官方文檔早期推崇的"一切皆組件"口號,被誤讀成"把所有東西塞進一個組件"。
2019年React Hooks發布后,函數組件成為主流,組合模式的實踐才逐漸普及。但歷史包袱還在——你能在2024年的生產代碼里找到大量class組件時代的"萬能組件"遺跡。
開閉原則的邊界在哪
嚴格遵循OCP需要預判變化方向。如果產品經理明確說"標題區域永遠只有這三種按鈕",第一種寫法就是合理的技術債。問題在于這種承諾在敏捷開發里幾乎不可信。
原文的解決方案也有成本。組件數量膨脹后,目錄結構需要設計。Title、TitleWithLink、TitleWithButton、TitleWithTooltip散落在各處,還是按功能聚合到title/目錄下?這關系到代碼可發現性。
更現實的折中是"預留擴展點但不提前實現"。基礎Title保留children,但先只實現一種具體形態。需求來了再擴展,比推倒重來便宜得多。
你的團隊現在怎么處理這類場景?是堅持組合模式,還是用條件分支硬扛?最近一次因為"加個type"導致的線上事故是什么時候?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.