React Redux 中間件思想遇見 Web Worker 的靈感(附demo)
寫在最前
熟悉 React 技術棧的同學,想必對 Redux 數據流框架並不陌生。其倡導的單向數據流等思想獨樹一幟,雖然樣板代碼會有一定程度上的增多,但是對於開發效率和調試效率的提高是顯著的。同時還帶來了很多諸如 「時間旅行」,「 undo/redo 」 等黑魔法。
其實這還只是表象。如果你深入去了解 Redux 的設計理念,探索中間件奧秘,玩轉高階 Reducer 等等,迎接你的就會是另一扇門。透過它,函數式編程思想之光傾斜如注。
思想背景
但是隨著這個 web app 複雜度的提升,數據計算量壓力徒增,你所設計的 Reducer 變得臃腫不堪。好吧,我們可以拆分 Reducer 使得代碼看上去更加舒服。可是計算量呢?也許有一些「夢魘」,瓶頸般永遠無法消除。
冥冥之中,「各種處理計算既然註定在同一時空,那麼能否永遠平行?」
曾幾何時,你是否聽說過 JS 單線程非同步?聽說過瀏覽器卡頓或卡死?聽說過 60 fps?
其實一個很嚴峻的事實是:根據 60 fps 計算,每一幀留給我們 JS 執行的時間為 16ms(甚至更少)。那麼一旦當 Reducer 計算時間過長,必然會影響瀏覽器渲染。
多線程思路
關於瀏覽器主線程、render queue、event loop、call stack 等內容,本文不再複述,因為裡面的知識完全都夠寫一本書了。假定讀者對其有一二認知,那麼你也不難理解我們即將登場的救星—— Web Worker!
我們先來簡單認識一下 web worker:
2008 年 W3C 制定出第一個 HTML5 草案開始,HTML5 承載了越來越多嶄新的特性和功能。其中,最重要的一個便是對多線程的支持。在 HTML5 中提出了工作線程(Web Worker)的概念,並且規範出 Web Worker 的三大主要特徵:
- 能夠長時間運行(響應);
- 理想的啟動性能;
- 以及理想的內存消耗。
Work 線程可以執行任務而不干擾用戶界面。
於是,腦洞大開,能否將我們的 Redux Reducer 計算狀態部分放進 Worker 線程中處理呢?
答案是肯定的。 那麼要如何實施呢?
我們先來看一下經典的 Redux workflow,如下圖:
如果要接入 Web Work,那麼我們改動流程圖如下:
具體實現和一個demo
當然,有了思路,還需要在實戰中演練。
我使用 「N-皇后問題」 模擬大型計算,並且實現的 demo 中可以任意設置 n 值,增加計算耗時。
如果你不理解此演算法也沒有關係,只需要知道N-皇后問題這個演算法的計算耗時很長,且和 n 值相關:n 越大,計算成本越大。除了一個極其耗時的計算,頁面中還運行這麼幾個模塊,來實現複雜的渲染邏輯操作:
- 一個實時每16毫秒,顯示計數(每秒增加1)的 blinker 模塊;
- 一個定時每500毫秒,更新背景顏色的 counter 模塊;
- 一個永久往複運動的 slider 模塊;
- 一個每16毫秒翻轉5度的 spinner 模塊
這些模塊都定時頻繁地更新 dom 樣式,進行大量複雜的渲染計算。正常情況下,由於 JS 主線程進行N-皇后計算,這些渲染過程都將被卡頓。
同時,我設置「N-皇后問題」的 n 值,來觀察在計算時這些模塊的表現(是否卡頓)。在不開啟 Work 線程的情況下,n 設置為13時,有 gif 圖,左半部分:
我們非常清晰地看到:由於瀏覽器 call stack 進行 n=13 的皇后問題計算,而無法「按時」渲染,所以造成了這幾個模塊的卡頓,這些模塊都無法更新狀態。在這個卡頓過程中,用戶的任何事件(如點擊,敲鍵盤等)都無法被瀏覽器響應。這就是用戶體會到的「慢」!
如果我把 n 值設置的大與13呢,比如24?
千萬不要這麼做!因為你的瀏覽器會被卡死!我使用 Mac Pro 8G 內存情況下,設置到14,瀏覽器就無法響應了。在開啟 Work 線程時,請參考上 gif 圖右半部分,幾個模塊的渲染絲毫不受影響。完美達到了我們的目的。
因為 Reducer 的超級耗時計算被放入 Worker 線程當中,所以絲毫沒有影響瀏覽器的渲染和響應。完全解決了用戶覺得「電腦慢」的問題。
看到了如此完美的對比,也許你想問 Web Worker 的兼容性如何呢?
總結
其實,這篇文章的意義並不在於這個 demo 和應用。而是在啟發一種新的想法的同時,review 了很多 JS 當中關鍵概念和基本知識。比如:單線程非同步、宿主環境、60 fps、一個演算法等等。
更值得一提的是,如果你去深入 demo 代碼,你更會發現 Redux 設計精妙的思想,比如我們將 Web Worker 的應用抽象出一個公共庫:Redux-Worker,並包裝為 Redux 的中間件(middleware),所有 React Redux 都可以無侵入,採用中間件的思想使用:
import { applyWorker } from redux-worker;nconst enhancerWithWorker = compose(n applyMiddleware(thunk, logger),n applyWorker(worker)n);nnconst store = createStore(rootReducer, {}, enhancerWithWorker);n
當然,Redux-Worker 這個中間件的實現原理更是巧妙,這裡不再展開。感興趣的同學可以參考我的此項目 Github 倉庫。我 fork 了此庫源碼,並在核心邏輯加入了中文注釋,感興趣的同學可以關注,並在本地跑起來進行理解消化。
我其他關於 React 文章:
- 通過實例,學習編寫 React 組件的「最佳實踐」
- React 組件設計和分解思考
- 從 React 綁定 this,看 JS 語言發展和框架設計
- React 服務端渲染如此輕鬆 從零開始構建前後端應用
- 做出Uber移動網頁版還不夠 極致性能打造才見真章
- 解析Twitter前端架構 學習複雜場景數據設計
- React Conf 2017 乾貨總結1: React + ES next = ?
- React+Redux打造「NEWS EARLY」單頁應用 一個項目理解最前沿技術棧真諦
- 一個react+redux工程實例
Happy Coding!
PS:
作者Github倉庫 和 知乎問答鏈接歡迎各種形式交流。推薦閱讀:
※Learn CSS Animations: From A Designer's Perspective
※為什麼在知乎上 React 的評價這麼低?
※如何理解虛擬DOM?
※SegmentFault 技術周刊 Vol.11 - React 應用與實踐