您是否厭倦了 React 應用程式中無休止的 props 鑽取和回調鏈?管理深度嵌套元件之間的狀態和通訊是否感覺就像與義大利麵條程式碼搏鬥?
事件驅動的架構可以簡化元件互動、降低複雜性並使應用程式更易於維護。在本文中,我將向您展示如何使用自訂useEvent
掛鉤來解耦元件並改善 React 應用程式之間的通訊。
讓我引導您完成它,讓我們從
注意:此機制不是要取代全域狀態管理系統,而是元件通訊流的替代方法。
在現代應用程式開發中,管理元件之間的狀態和通訊很快就會變得很麻煩。在涉及props 鑽探(其中資料必須透過多層嵌套元件向下傳遞)和回調鏈的場景中尤其如此,這可能會導致邏輯混亂並使程式碼更難以維護或偵錯。
這些挑戰通常會建立緊密耦合的元件,降低靈活性,並增加試圖追蹤資料如何流經應用程式的開發人員的認知負擔。如果沒有更好的方法,這種複雜性會顯著減慢開發速度並導致程式碼庫脆弱。
在典型的 React 應用程式中,父元件將 props 傳遞給子元件,子元件透過觸發回呼與父元件進行通訊。這對於淺層元件樹來說效果很好,但隨著層次結構的加深,事情開始變得混亂:
Props 鑽取:資料必須透過多個層級的元件手動向下傳遞,即使只有最深層的元件需要它。
回調鏈:同樣,子元件必須將事件處理程序沿著樹向上轉發,從而建立緊密耦合且難以維護的結構。
以這個場景為例:
父級將 props 傳遞給子級 A 。
從那裡,道具會深入到GrandChildren A/B並最終深入SubChildren N 。
如果SubChildren N需要向 Parent 通知某個事件,它會觸發一個回調,該回調會透過每個中間元件向上傳播。
隨著應用程式的成長,此設定變得更難管理。中間元件通常只是充當中間人,轉發 props 和回調,這會使程式碼變得臃腫並降低可維護性。
為了解決props 鑽探問題,我們經常求助於全域狀態管理庫(例如Zustand )等解決方案來簡化資料共享。但是如何管理回調呢?
這就是事件驅動方法可以改變遊戲規則的地方。透過解耦元件並依靠事件來處理交互,我們可以顯著簡化回調管理。讓我們探討一下這種方法是如何運作的。
事件驅動的架構不是依賴直接回調在樹上進行通信,而是解耦元件並集中通信。它的工作原理如下:
當SubChildren N觸發事件(例如onMyEvent)時,它不會直接呼叫Parent 中的回呼。
相反,它調度由集中式事件處理程序處理的事件。
事件處理程序偵聽分派的事件並處理它。
它可以通知父級(或任何其他感興趣的元件)或根據需要觸發其他操作。
道具仍然沿著層次結構傳遞,確保元件接收到它們運作所需的資料。
這可以透過 zustand、redux 等集中式狀態管理工具來解決,但本文不會討論。
但是,我們要如何實現這個架構呢?
讓我們建立一個名為useEvent 的自訂鉤子,該鉤子將負責處理事件訂閱並傳回一個調度函數來觸發目標事件。
由於我使用打字稿,我需要擴展視窗Event
介面才能建立自訂事件:
interface AppEvent<PayloadType = unknown> extends Event {
detail: PayloadType;
}
export const useEvent = <PayloadType = unknown>(
eventName: keyof CustomWindowEventMap,
callback?: Dispatch<PayloadType> | VoidFunction
) => {
...
};
透過這樣做,我們可以定義自訂事件映射並傳遞自訂參數:
interface AppEvent<PayloadType = unknown> extends Event {
detail: PayloadType;
}
export interface CustomWindowEventMap extends WindowEventMap {
/* Custom Event */
onMyEvent: AppEvent<string>; // an event with a string payload
}
export const useEvent = <PayloadType = unknown>(
eventName: keyof CustomWindowEventMap,
callback?: Dispatch<PayloadType> | VoidFunction
) => {
...
};
現在我們定義了所需的接口,讓我們看看最終的鉤子程式碼
import { useCallback, useEffect, type Dispatch } from "react";
interface AppEvent<PayloadType = unknown> extends Event {
detail: PayloadType;
}
export interface CustomWindowEventMap extends WindowEventMap {
/* Custom Event */
onMyEvent: AppEvent<string>;
}
export const useEvent = <PayloadType = unknown>(
eventName: keyof CustomWindowEventMap,
callback?: Dispatch<PayloadType> | VoidFunction
) => {
useEffect(() => {
if (!callback) {
return;
}
const listener = ((event: AppEvent<PayloadType>) => {
callback(event.detail); // Use `event.detail` for custom payloads
}) as EventListener;
window.addEventListener(eventName, listener);
return () => {
window.removeEventListener(eventName, listener);
};
}, [callback, eventName]);
const dispatch = useCallback(
(detail: PayloadType) => {
const event = new CustomEvent(eventName, { detail });
window.dispatchEvent(event);
},
[eventName]
);
// Return a function to dispatch the event
return { dispatch };
};
useEvent
掛鉤是一個自訂 React 掛鉤,用於訂閱和分派自訂視窗事件。它允許您監聽自訂事件並使用特定的負載觸發它們。
我們在這裡所做的非常簡單,我們使用標準事件管理系統並對其進行擴展以適應我們的自訂事件。
eventName
(字串):要偵聽的事件的名稱。
callback
(可選):觸發事件時呼叫的函數,接收負載作為參數。
事件監聽器:它監聽指定的事件並使用事件的detail
(自訂負載)呼叫提供的callback
。
調度事件:該鉤子提供了一個dispatch
函數來使用自訂負載觸發事件。
const { dispatch } = useEvent("onMyEvent", (data) => console.log(data));
// To dispatch an event
dispatch("Hello, World!");
// when dispatched, the event will trigger the callback
好吧,很酷,但是,怎麼樣
查看此 StackBlitz(如果未加載,請在此處查看)
這個簡單的範例展示了useEvent
掛鉤的用途,基本上,主體的按鈕正在調度從側邊欄、頁首和頁尾元件攔截的事件,並相應更新。
這讓我們可以定義因果反應,而無需將回呼傳播到許多元件。
筆記
如同評論中所指出的,請記住使用useCallback
記住回呼函數,以避免連續的事件刪除和建立,因為回呼本身將是useEvent
內部 useEffect 的依賴項。
useEvent
的真實用例以下是一些實際用例,其中useEvent
掛鉤可以簡化 React 應用程式中的通訊並解耦元件:
通知系統通常需要全球通訊。
設想:
當 API 呼叫成功時,需要在應用程式中顯示「成功」通知。
標題中的「通知徽章」等元件也需要更新。
解決方案:使用useEvent
掛鉤來調度帶有通知詳細資訊的onNotification
事件。像NotificationBanner
和Header
這樣的元件可以監聽這個事件並獨立更新。
當使用者切換主題(例如,亮/暗模式)時,多個元件可能需要回應。
設想:
ThemeToggle
元件調度自訂onThemeChange
事件。
側邊欄和標題等元件偵聽此事件並相應地更新其樣式。
優點:無需透過整個元件樹的 props 傳遞主題狀態或回呼函數。
實作全域快速鍵,例如按「Ctrl+S」儲存草稿或「Escape」關閉模式。
設想:
全域 keydown 偵聽器調度帶有按下的按鍵詳細資料的onShortcutPressed
事件。
模態元件或其他 UI 元素會回應特定的快速鍵,而不依賴父元件轉送按鍵事件。
聊天應用程式或即時儀表板等應用程式需要多個元件來對即時更新做出反應。
設想:
當新資料到達時,WebSocket 連線會調度onNewMessage
或onDataUpdate
事件。
聊天視窗、通知和未讀訊息計數器等元件可以獨立處理更新。
對於具有多個部分的複雜表單,可以集中驗證事件。
設想:
當使用者填寫欄位時,表單元件調度onFormValidate
事件。
摘要元件偵聽這些事件以顯示驗證錯誤,而不與表單邏輯緊密耦合。
追蹤使用者互動(例如按鈕點擊、導航事件)並將其發送到分析服務。
設想:
調度帶有相關詳細資訊的onUserInteraction
事件(例如,按一下的按鈕的標籤)。
中央分析處理程序偵聽這些事件並將它們傳送到分析 API。
對於共享白板或文件編輯器等協作工具,事件可以管理多用戶互動。
設想:
每當使用者繪製、鍵入或移動物件時調度onUserAction
事件。
其他用戶端和 UI 元件會偵聽這些事件以即時反映變更。
透過在這些場景中利用useEvent
掛鉤,您可以建立模組化、可維護且可擴展的應用程式,而無需依賴深度嵌套的 props 或回調鏈。
事件可以透過降低複雜性和提高模組化來改變您建立 React 應用程式的方式。從小事做起——確定應用程式中的一些元件,這些元件將受益於解耦通訊並實現 useEvent 掛鉤。
透過這種方法,您不僅可以簡化程式碼,還可以使其在未來更易於維護和擴展。
為什麼要使用事件?
當您需要元件對應用程式中其他地方發生的事情做出反應,而不引入不必要的依賴項或複雜的回調鏈時,事件就會發揮作用。這種方法減少了認知負擔並避免了緊密耦合元件的陷阱。
我的推薦
使用事件進行元件間通訊-當一個元件需要通知其他元件有關操作或狀態變更時,無論它們在元件樹中的位置為何。
避免使用事件進行元件內通信,特別是對於密切相關或直接連接的元件。對於這些場景,請依賴 React 的內建機制,如 props、state 或 context。
平衡的方法
雖然事件的力量很強大,但過度使用它們可能會導致混亂。明智地使用它們來簡化鬆散連接元件之間的通信,但不要讓它們取代 React 管理本地互動的標準工具。
原文出處:https://dev.to/nicolalc/event-driven-architecture-for-clean-react-component-communication-fph