iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0

前情提要

接續前一篇的 Reactivity 核心概念講解內容,我們透過這一篇的內容帶大家釐清 Pull-based 與 Push-based 這兩個模式差異。

核心概念

在細顆粒度(fine-grained)reactivity 裡:

  • Push-based 把「計算」提早到寫入當下完成。
  • Pull-based 把「計算」延遲到真正有人讀取才進行。

生活中案例

Push-based

我以百貨公司美食街的購物情境為例子,當我們買完餐後,會拿到一個圓盤。

  • 寫入(下單):你點餐完成。
  • 推送(完成即通知):餐做好時,系統主動讓圓盤震動/亮燈,把更新一路「推」到你手上。
  • 副作用(取餐):你收到訊號就去櫃檯領餐。

對應 reactivity:來源改變就沿依賴鏈立即重算並通知後續節點。

Pull-based

我以買手搖茶的購物情境為例子,當我們點完餐後,會拿到一個單子上面有號碼。

  • 寫入(下單):你點飲料完成。
  • 標記(只更新狀態):飲料做好時,店家只把你的號碼掛在螢幕上(狀態已變更,但沒直接通知你)。
  • 讀取→重算(查看才發生):你需要領取時抬頭查看;如果看到到號,這次讀取才觸發「我要去拿」的行動。
  • 副作用(取餐):看到到號後去櫃檯領取。

對應 reactivity:寫入時只標記髒標記,讀取時才做必要的計算與更新。

核心定義

名稱 行為焦點 簡化流程
Push-based 寫入即計算:資料一改動就沿依賴鏈立刻遞迴重算 set() → propagate → compute → effect
Pull-based 寫入只標記:先把依賴節點標成 dirty,真正重算延後到被讀取 set() → markDirtyread() → if dirty then compute → effect

重點觀念: 兩者都會「推送」訊號,但 push 推的是『計算』,pull 推的是『標記』。

時序流程

Push-based

https://ithelp.ithome.com.tw/upload/images/20250805/20129020sTny6WN8x3.png

Pull-based

https://ithelp.ithome.com.tw/upload/images/20250805/20129020THYHE5H0Jx.png

個別優缺點比較

面向 Push-based Pull-based
讀取延遲 最低,資料永遠新 若節點已髒,第一次讀取需重算
寫入成本 潛在大,O(依賴深度 × 寫入次數) 較小,幾乎 O(依賴度 x 1),僅標記
過度計算 多,即使結果沒被讀取 少,只在實際讀取時才算
批次化 較難:事先已算完 天生適合:可一次性 flush
除錯可觀測 依賴鏈一次展開 需 DevTools 追蹤何時 pull
適合場景 高寫入頻率、低讀取 —— 例如協同編輯游標位置 低寫入、高讀取 —— 例如資料儀表板、視覺化

實務上仍需考慮「標記」時,沿依賴樹向下走一次,但通常只設 dirty=true 而非重算。

那該怎麼抉擇?

還是要看開發場景和需求來決定,現在主流的 library 提供的解法,就是盡量結合這兩者之間的好處。

典型需求 建議模式 說明
即時共同編輯、遊戲狀態同步 Push 每次寫入都應立即反映,避免視覺延遲;依賴鏈淺、節點少
大型監控面板、圖表篩選 Pull 寫入少但讀取密集;Pull 把算力花在「真正被看見」的數值上
行為-驅動動畫(timeline、scroll-based) Pull + Scheduler Pull 延遲計算,加上排程器可保證同一 frame 只重算一次
資料科學 Pipeline(overkill 算一次可 reuse) Push-on-Commit 一次性執行、產物被多方重用;先算完再分發

以 React 為例,本質上屬於 Pull + Scheduler 的模式,這部分可以回想一下官方提及的 batching update 章節,我這裡就不再贅述了,至於 Push-on-Commit 的典型範例就是 Rxjs / MobX 了。

常見迷思澄清

  • Pull 就一定要全域掃描?
    • 不必。Pull 只在被讀取時針對該依賴鏈向上檢查版本/髒標記,不需要整樹 diff。
  • Push 必定浪費計算?
    • 當寫入後確定馬上被讀取(如協同編輯游標),先算能換取最低讀取延遲,相當划算。
  • Push 與 Pull 只能二選一?
    • 多數現代 signal framework 其實採 Hybrid (push-pull):
      • 寫入階段 push 髒標記
      • 讀取階段 pull 重算,這兼具標記即時性與惰性計算優點(後面實作環節將深入細節釐清)。
  • 即使在最細顆粒度的系統裡,為什麼仍需要「Pull」?
    • 因為我們常常不知道「會不會被用到」和「何時用到」。
    • Pull 讓「資料變了」與「要不要做事」分離:先標髒標記、等讀取時再重算。

結語

為什麼 Fine-grained Reactivity 要討論 Push vs. Pull?

在 coarse-grained(React V-DOM)世界,整棵樹 diff 已經足夠抽象。
但進到 signal-based 細顆粒度,一行 set 可能波及數百個微小 derivations。
此時,「計算發生的時機」 直接決定:

  • 計算總量(性能瓶頸)
  • 互動延遲(體感流暢度)
  • 排程策略(避免抖動與掉幀)

搞懂 Push 與 Pull 的本質對照,你就有了評估與選型的底圖。

下一篇,我們來看看主流框架如何處理 Reactivity 的解決方案。


上一篇
Reactivity 的概念與演進
系列文
Reactivity 小技巧大變革:掌握 Signals 就這麼簡單!4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言