淺談React 組件的組合模式之道(Composition Pattern)
基于 React Universe Conf 2025 中 Fernando Rojo 的演講《Composition is all you need》,以下是關(guān)于如何使用**組合模式(Composition Pattern)**重構(gòu)復(fù)雜 React 組件的教程和代碼總結(jié)。
React 組件的組合模式之道 (Composition Pattern)
1. 核心問題:單體組件的陷阱 (The Monolith Trap)
在開發(fā)初期,我們通常創(chuàng)建一個(gè)簡單的組件(如 Composer 輸入框)。隨著需求增加(支持多態(tài)、編輯模式、轉(zhuǎn)發(fā)模式等),我們往往通過添加 Boolean 屬性來控制功能。
反模式代碼示例:
// ? 典型的“單體”組件,充滿條件判斷
function Composer({
onSubmit,
isThread,
isEditingMessage,
initialText,
onCancel
}) {
return (
<div className="composer">
{/* 只有非編輯模式才支持拖拽 */}
{!isEditingMessage && <DropZone />}
<Header />
<Input defaultValue={initialText} />
{/* 線程模式下的額外選項(xiàng) */}
{isThread && <Checkbox label="Also send to channel" />}
<Footer>
{/* 只有非編輯模式才顯示附件按鈕 */}
{!isEditingMessage && <AttachmentButton />}
{/* 提交按鈕邏輯復(fù)雜 */}
{isEditingMessage ? (
<>
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={onSubmit}>Save</Button>
</>
) : (
<Button onClick={onSubmit}>Send</Button>
)}
</Footer>
</div>
);
}
缺點(diǎn): 代碼難以維護(hù),條件渲染(Ternary Hell)泛濫,且容易出現(xiàn)不可能的狀態(tài)組合。
2. 解決方案:組合模式 (Composition)
與其通過屬性(Props)告訴組件做什么,不如通過子組件(Children)直接構(gòu)建組件。這種方式類似于 Radix UI 的設(shè)計(jì)理念。
我們將大組件拆分為多個(gè)小的、職責(zé)單一的子組件,并通過 Context 共享狀態(tài)。
基礎(chǔ)架構(gòu)代碼
// 1. 創(chuàng)建 Context
const ComposerContext = createContext(null);
// 2. Provider 組件:管理狀態(tài)和對(duì)外接口
const ComposerProvider = ({ children, state, actions, meta }) => {
return (
<ComposerContext.Provider value={{ state, actions, meta }}>
{children}
</ComposerContext.Provider>
);
};
// 3. 子組件:消費(fèi) Context
const ComposerInput = () => {
const { state, actions } = useContext(ComposerContext);
return (
<input
value={state.text}
onChange={(e) => actions.update(e.target.value)}
/>
);
};
// ... 其他子組件 (Composer.Header, Composer.Footer, etc.)
3. 實(shí)戰(zhàn)重構(gòu):構(gòu)建不同的 Composer
通過組合,我們可以在不修改內(nèi)部邏輯的情況下,構(gòu)建出完全不同的 UI 變體。
場景 A:基礎(chǔ)頻道輸入框 (Channel Composer)
function ChannelComposer() {
// 使用自定義 Hook 獲取全局頻道邏輯
const { state, actions } = useChannelLogic();
return (
<Composer.Provider state={state} actions={actions}>
<Composer.DropZone />
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
{/* 使用封裝好的通用操作組 */}
<Composer.CommonActions />
<Composer.SubmitButton />
</Composer.Footer>
</Composer.Frame>
</Composer.Provider>
);
}
場景 B:編輯消息輸入框 (Edit Message Composer)
需求差異:
- 不需要拖拽上傳 (
DropZone)。 - 底部按鈕不同(取消/保存)。
- 某些操作按鈕不可見(如附件)。
組合實(shí)現(xiàn): 我們只需要不渲染不需要的組件,并替換底部的按鈕即可,無需任何 Boolean 屬性。
function EditMessageComposer({ messageId, initialText, onCancel }) {
const { state, actions } = useEditMessageLogic(messageId, initialText);
return (
<Composer.Provider state={state} actions={actions}>
{/* 移除 DropZone */}
<Composer.Frame>
<Composer.Header />
<Composer.Input />
<Composer.Footer>
{/* 手動(dòng)列出需要的 Action,而不是用通用的 */}
<Composer.FormatText />
<Composer.Emoji />
{/* 自定義底部按鈕布局 */}
<div className="flex gap-2">
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={actions.submit}>Save</Button>
</div>
</Composer.Footer>
</Composer.Frame>
</Composer.Provider>
);
}
4. 進(jìn)階技巧:狀態(tài)提升與解耦 (Lift Your State)
這是該演講最核心的觀點(diǎn)。狀態(tài)管理應(yīng)該與 UI 組件解耦。
Composer 的 UI 組件(Input, Footer 等)不應(yīng)該知道狀態(tài)是來自于 useState(本地狀態(tài))還是 useGlobalStore(全局同步狀態(tài))。它們只負(fù)責(zé)渲染 Provider 提供的數(shù)據(jù)。
場景 C:轉(zhuǎn)發(fā)消息 (Forward Message)
復(fù)雜點(diǎn):
- 這是一個(gè)模態(tài)框(Modal)。
- 提交按鈕在 Composer 外部(Modal 的 Footer)。
- 狀態(tài)是臨時(shí)的(Ephemeral),不需要同步到服務(wù)器。
代碼實(shí)現(xiàn):
function ForwardMessageDialog() {
// 1. 狀態(tài)提升:在父組件控制狀態(tài)
const [text, setText] = useState("");
const inputRef = useRef(null);
// 定義符合 Provider 接口的 state 和 actions
const state = { text };
const actions = {
update: setText,
submit: () => console.log("Forwarding:", text)
};
return (
<Dialog>
{/* 2. 將本地狀態(tài)注入 Provider */}
<Composer.Provider state={state} actions={actions} meta={{ inputRef }}>
{/* UI 部分 */}
<Composer.Frame>
<Composer.Input />
<Composer.Footer>
{/* 只有少量的操作按鈕 */}
<Composer.Emoji />
</Composer.Footer>
</Composer.Frame>
{/* 3. 外部按鈕也可以消費(fèi)同一個(gè) Context */}
<Dialog.Footer>
<CopyLinkButton />
{/* 這個(gè)按鈕在 Composer 外部,但能觸發(fā)提交 */}
<ForwardButton />
</Dialog.Footer>
</Composer.Provider>
</Dialog>
);
}
// 外部按鈕實(shí)現(xiàn)
const ForwardButton = () => {
// 因?yàn)楸话?Composer.Provider 內(nèi),依然可以訪問 context
const { actions } = useContext(ComposerContext);
return <Button onClick={actions.submit}>Forward</Button>;
}
總結(jié):為什么要這樣做?
- 消除“布爾地獄”: 不再需要傳遞
isEditing={true}、isForwarding={true}并在組件深處做判斷。需要什么功能,就渲染什么組件。 - 狀態(tài)靈活性: 同一套 UI 組件可以配合
useState(本地)、Redux/Zustand(全局)甚至Ref一起工作,只要通過 Provider 傳入即可。 - 可維護(hù)性: 當(dāng)需要在“轉(zhuǎn)發(fā)”功能中修改按鈕樣式時(shí),你只需要修改
ForwardMessageDialog,完全不會(huì)影響到“頻道聊天”的代碼。 - AI 友好: 這種結(jié)構(gòu)化、聲明式的代碼更容易被 AI 理解和生成,減少了 AI 產(chǎn)生幻覺(Hallucination)或邏輯錯(cuò)誤的概率。
一句話總結(jié): 不要把所有的邏輯塞進(jìn)一個(gè)組件里。提升你的狀態(tài) (Lift your state),組合你的內(nèi)部組件 (Compose your internals)。
到此這篇關(guān)于淺談React 組件的組合模式之道(Composition Pattern)的文章就介紹到這了,更多相關(guān)React 組件組合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React中setTimeout獲取不到最新State值的原因及解決方案
在 React 開發(fā)中,我們常常需要在異步操作中訪問組件的 State,然而,由于 React 的閉包機(jī)制和異步更新特性,setTimeout?中可能會(huì)獲取到過時(shí)的 State 值,本文將深入解析這一現(xiàn)象的原因,并提供多種解決方案,需要的朋友可以參考下2025-10-10
react項(xiàng)目自行配置熱更新的實(shí)現(xiàn)
本文主要介紹了react項(xiàng)目自行配置熱更新的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
vite?+?react?+typescript?環(huán)境搭建小白入門教程
這篇文章主要介紹了vite?+?react?+typescript?環(huán)境搭建小白入門教程,本文通過示例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12
React中不適當(dāng)?shù)腍ooks使用問題及解決方案
在 React 開發(fā)中,Hooks 提供了一種強(qiáng)大的方式來管理組件的狀態(tài)和生命周期,然而,不恰當(dāng)?shù)?nbsp;Hooks 使用可能會(huì)導(dǎo)致組件行為異常、性能問題或難以調(diào)試的錯(cuò)誤,本文將探討 React 中常見的不適當(dāng) Hooks 使用問題,并提供解決方案,需要的朋友可以參考下2025-03-03
React使用UI(Ant?Design)框架的詳細(xì)過程
Ant?Design主要用于中后臺(tái)系統(tǒng)的使用,它提供了豐富的組件和工具,可以幫助開發(fā)人員快速構(gòu)建出美觀、易用的界面,同時(shí),Ant?Design還提供了詳細(xì)的文檔和示例,方便開發(fā)者學(xué)習(xí)和使用,這篇文章主要介紹了React使用UI(Ant?Design)框架,需要的朋友可以參考下2023-12-12
一百多行代碼實(shí)現(xiàn)react拖拽hooks
這篇文章主要介紹了一百多行代碼實(shí)現(xiàn)react拖拽hooks,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

