常見的 React 庫架構設計

React 以其強大的生態系統而聞名。我們有各種各樣的工具來處理狀態管理、表單、路由、樣式等,但它們在內部是如何工作的呢?

其實,大多數工具在架構選擇上都有相似之處,更傾向於遵循 React 的工作方式。接下來我們來探討一下這些常見的結構。

一、綁定機制(Binding)

大多數庫的架構通常分爲兩個主要部分:核心(core)和綁定(binding)。核心部分負責邏輯和功能的實現,而綁定則負責將核心與前端工具連接起來。在 React 中,這個連接通常表現爲組件和自定義 hooks。

在很多工具中,核心對象都是在組件外部創建的。這麼做有一個好處,就是可以避開 React 的重新渲染週期,也無需關注 React 的記憶優化機制(memoization)。接下來,要與 React 連接,通常是通過 Context API 來實現的。

示例:Zustand 的外部 store 設計

// store.ts
import{ create }from'zustand';

exportconst useStore =create((set)=>({
count:0,
increment:()=>set((state)=>({count: state.count +1})),
}));

然後在組件中使用:

const count =useStore((state)=> state.count);

這也是爲什麼那麼多庫中都有 Provider(提供器)組件。這些 Provider 組件的作用就是將核心的數據注入到整個組件樹中。雖然 Context API 對動態數據的處理存在性能問題,但由於核心對象是穩定引用的,所以並不會帶來太大問題。

因此,只要把其他組件或 hooks 放在 Provider 之下,就可以直接使用核心的數據和功能,即使你並沒有直接傳遞這些內容。比如 Tanstack Query,你只需要創建一個核心 client 並通過 Provider 提供,就可以在不同頁面中使用相同的查詢緩存,只要 key 一樣即可。不需要手動共享數據,庫會通過核心和 Context Provider 自動完成這一連接。

示例:TanStack Query 的使用

<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>

組件中使用:

const{ data }=useQuery(['user'], fetchUser);

你也可以在像 React-router 這樣的庫中看到類似的結構,你調用 useParamsuseNavigate 等 hook 時,其實就是從路由核心中獲取數據和方法,而這些 hook 底層是通過 Context 來連接核心的。

二、外部連接(External connection)

不過接下來就面臨另一個問題:客戶端需要有某種機制來與 React 的渲染模型進行連接。React 怎麼知道客戶端的某些狀態發生了變化,並需要重新渲染來獲取最新的值呢?

大多數庫會使用 “觀察者模式”(Observer pattern)。這種模式通常包含三個核心方法:

雖然庫的核心中可能會有更多方法,但這三個是基本的組成部分。

要將這些外部數據與 React 連接,有兩種方式:

1、useSyncExternalStore:這是 React 提供的原生 hook,專門用於處理外部狀態。你需要向它傳入核心的 getState 和 subscribe 方法(如果支持服務端渲染,還需要額外的 getServerSnapshot),它會自動訂閱變更並觸發重新渲染,從而返回最新數據。

const value =useSyncExternalStore(store.subscribe, store.getState);

2、自定義 hook:你也可以使用 useEffect 和 useReducer 來實現一個自己的 hook。在 useSyncExternalStore 出現之前,大家通常都是用這種方式,它也有自己的優勢。

function useCustomStore(store){
const[state, setState]=useState(store.getState());

useEffect(()=>{
const unsubscribe = store.subscribe(setState);
return unsubscribe;
},[store]);

return state;
}

需要注意的是,useSyncExternalStore 已被反優化,這意味着它會被 React 視爲高優先級更新,這會影響到應用中併發特性的優化。不過,這種行爲在某些場景下是你想要的,因爲它可以避免 “撕裂”(tearing)—— 也就是組件使用了舊的狀態數據。

雖然 React 在併發特性中默認允許某些狀態撕裂,以優先渲染更重要的內容,但對於外部狀態,React 認爲撕裂是不好的。因此,一旦外部狀態變更,它會立即同步更新並重新渲染,確保使用的是最新狀態。

如果你自己實現連接邏輯,就可以避開這種高優先級更新,把外部狀態當作低優先級處理。這樣的話,就可以暫停上一次的更新渲染,去先處理其他交互邏輯。

這是一個需要權衡的選擇,但目前大多數庫都傾向於使用 useSyncExternalStore 方案。

三、React 19

React 當前的最新版本(React 19)帶來了一些新特性,會影響很多庫的實現方式。其中,處理 Suspense 和 Promise 的原始 hook 會被大量使用,但在某些場景下,也可能需要用到更多的新 hook。

比如表單庫,就可以很好地利用 useOptimisticuseFormStatus 和 useActionState 這些新 hook,尤其是在使用 React Server Components 的框架中,比如 Next.js、Waku 和 Tanstack Start。

XrJ6aH

示例:使用 useFormStatus 控制按鈕狀態

function SubmitButton(){
const{ pending }=useFormStatus();
return<button disabled={pending}>{pending ?'提交中...':'提交'}</button>;
}

四、常見狀態管理庫架構對比

雖然大多數庫都遵循 “核心 + 綁定” 模型,但具體實現存在差異:

ykgsK8

這些架構會隨着具體場景的不同而不斷演進和調整,但總體來看,這就是當前 React 生態系統中常見的結構設計思路。

五、架構選型建議

6v73on

譯者:@飄飄
作者:@Felipe
原文:https://www.felgus.dev/blog/common-react-lib-architecture

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BlGfeubwgTIzTBhRkOGmzg