在上一篇中,我們拆解了 Dependency Tracking 的核心概念與執行原理,本篇將焦點放在 React 的 dependency 模型:它的特性、限制,以及與 Signals 顯式依賴圖的對比。
React 並沒有內建顯式的依賴追蹤圖,它的運作模式是:
這是一種 Pull-based + Scheduler 模式:React 並不知道某個 state 與哪些 UI 節點直接相關,只能「重新拉一次整個 component」來確保正確性。
問題類型 | 描述 | 影響 |
---|---|---|
缺乏顯式依賴圖 | React 無法精準知道哪個 state 對應到哪段 UI | 導致不必要的 re-render |
依賴陣列手動維護 | useEffect 必須手動列出依賴,錯漏容易發生 | 造成 bug 或無謂的 effect 觸發 |
重複計算 | 多個 component 讀取相同 state 時,各自觸發渲染 | 效能浪費,尤其在大型應用 |
難以處理動態依賴 | 依賴關係需手動管理,條件式邏輯繁瑣 | 增加維護成本 |
Signals(以 Solid.js、MobX、Vue ref 為例)在這些方面有所改善:
function Counter() {
const [count, setCount] = useState(0);
const double = count * 2; // 每次渲染都重算
return (
<>
<h1>Hello World!</h1>
<button onClick={() => setCount(c => c + 1)}>{double}</button>;
</>
);
}
count
更新 → Counter 整個重新渲染(包含Hello World) → double
重算。const [count, setCount] = createSignal(0);
const double = () => count() * 2;
function Counter() {
return (
<>
<h1>Hello World!</h1>
<button onClick={() => setCount(c => c + 1)}>{double()}</button>;
</>
);
}
count
更新 → 只重算 double
→ 只更新使用它的 DOM 節點(只有 button 會被更新)。雖然 React 缺乏顯式 dependency tracking,但它藉由以下策略緩解問題:
key
、memo
)React.memo
、useMemo
、useCallback
)這些補救方案雖然提升了效能,但開發者仍需手動調整最佳化。
所以常會聽到:
「React 只是 library,library 怎麼會處理那些任務,那是 framework 的工作。」
「Batching 很正常吧! 有程式語言基礎的都應該要知道阿!」
「JS 程度不好就先練,不要抱怨那些 Javascript 基礎觀念的東西!」
這些話確實會讓一些新手鬼轉其他框架,這樣的態度也讓他們總是不找找自己問題!
檢討開發者,先貶低新手,再推銷一波課程:
我手邊有批「React 效能優化實戰」的課程,能讓你擺脫菜味,很便宜的,有沒有興趣參考一下!
這樣的養套殺,培養了一坨很大的教學產業鏈,死忠的支持者永遠會為自己找到合適的藉口。
以上純屬現象觀察,沒有要引戰,我也是從一個懵懵懂懂的 React 仔慢慢走過來的。
從今天的這篇介紹,我們可以得到下列重點:
在對效能敏感或依賴關係複雜的應用中,Signals 模型有顯著優勢。
下一篇,我們從實作層面來慢慢推導出一個簡易的 Signal 範例。
寫 react 的真的整天都在談優化XD
我的觀察是,會談效能優化議題的,都只限於 React 生態,在最後一定會推銷一下自己的付費課程。