寫本文的原因
- 有幾位小夥伴最近又來問這個問題,之前幫人解答過一次,今天寫下來
- 以後有時間會多寫一些解決方案,例如oom了,不用esbuild怎麼解決之類的等..
正式開始
主題:股票交易APP(IM場景前端互動高頻更新卡頓)
- 一個正常的股票交易APP,是很複雜的,大都用原生寫,但是有的公司沒錢啊,只能做一套web app或者用RN這些寫,也有用Flutter的(這就是沒錢又要玩,那怎麼辦呢?那就玩
乞丐版
呀)
一個股票交易APP的介面長這樣
-
首先金融交易類產品是IM產品的一種,大都使用私有基於TCP長連結私有協議或者wss協議,這裡推薦兩篇我之前寫的文章,這樣你來看本文效果會比較好。
- 手寫實現一個websocket協議(基於Node.js)
- 手寫一個React框架
問題重現
- 使用者收藏了1000只自選股(國內國外+期貨+指數等),技術棧是web app ,基於react或React-native,很卡頓
- 由於是雙工通訊,而且高頻推送,觸發更新,而且交易類APP對訊息送達的效率/低延遲要求非常高,例如你準備買這隻股票,此時大戶砸盤,你還沒收到更新的資訊,下單,發現趨勢已經走壞,然後接盤被套。
- 還有一種情況,你買入的時候出了大利好,你下單價格是10塊錢,但是此時已經漲到10.05,這個價格成成交不了,然後你錯過了一波大漲。這時候客戶就慘了
需求簡單&技術的剖析
- 理論上使用者可以新增的自選股票,是無限的
- 每個自選股/股票的都有對應的事件觸發
- 高頻更新,此時要區分react/react-native環境,因為react-native元件在掛載後就不會解除安裝了,不像web app.
原則
- 效能優化最好是簡單的手段
- 所見即所得,簡單高校,不觸碰底層邏輯,例如網路層前後端可能都要做粘包的處理
- ...不做可能誘發P0級別事故的技術方向選擇
解決問題
- react/react-native渲染上有區別,對於長列表,react-native是有元件對應只渲染可視區域,react則不會,需要虛擬列表,推薦
react-peter-window
這個庫,而且可以支援自動高寬
原始碼demo地址:https://github.com/JinJieTan/react-keepAlive-dynamic
- 這樣react也可以跟react-native的元件一樣,只渲染可視區域了
- 長列表問題解決了,但是事件同時也很麻煩,理論上使用者可以新增無限的自選股,這個列表可能就有無限長(不要說不可能,世界在發展,這就是高可用的APP),傳統的事件需要每個item去繫結,然後切換元件時候再remove掉,但是頻繁對事件掛載、移除其實也很損耗效能,這裡換成事件冒泡,就可以了,把需要的資料掛載到dom的屬性上獲取即可~
- 上面說的,不要小看,能解決相當一部分效能問題
最重要的高頻更新的問題
- 不同金融交易類公司,後端架構設計不一樣,訊息推送也是,例如大智慧的後端架構就比較特殊.
- 前端網路層可能要處理粘包,後端的訊息推送頻率我們不管
- 借鑑PReact、Redis、kafka的思想,自己在前端實現一個
訊息佇列
,定期消費,更新介面. - 參考我之前手寫React的程式碼:
`https://github.com/JinJieTan/mini-react/tree/hooks
import { _render } from '../reactDom/index';
import { enqueueSetState } from './setState';
export class Component {
constuctor(props = {}) {
this.state = {};
this.props = props;
}
setState(stateChange) {
const newState = Object.assign(this.state || {}, stateChange);
console.log(newState,'newState')
this.newState = newState;
enqueueSetState(newState, this);
}
}`
- 當setState後,先進入佇列中,首次進入,佇列為空,進入判斷,下一幀渲染前呼叫defer(flush)
`export function enqueueSetState(stateChange, component) {
//第一次進來肯定會先呼叫defer函式
if (setStateQueue.length === 0) {
//清空佇列的辦法是非同步執行,下面都是同步執行的一些計算
defer(flush);
}
//向佇列中新增物件 key:stateChange value:component
setStateQueue.push({
stateChange,
component,
});
//如果渲染佇列中沒有這個元件 那麼新增進去
if (!renderQueue.some((item) => item === component)) {
renderQueue.push(component);
}
}`
- defer函式
`function defer(fn) {
//高優先順序任務 非同步的 先掛起
return requestAnimationFrame(fn);
}`
- 此時訊息再次推送,再次觸發enqueueSetState,資料此時被推送到佇列中,一幀統一合併消費。
❝其實瀏覽器也是有渲染佇列的,例如你在一個for迴圈裡面頻繁操作dom,並不會每次操作dom都會導致瀏覽器渲染,達到一個閥值,就會觸發渲染,當然你也可以手動控制清空佇列(這裡不寫太深,有興趣的可以關注後面的文章)
❞
寫在最後
- 我是Peter,架構設計過20萬人端到端加密超級群功能的桌面IM軟體,現在是一名前端架構師。
- 如果你對效能優化有很深的研究,可以跟我一起交流交流,今天這裡寫得比較淺,但是大部分人都夠用,之前問我的朋友,我讓它寫了一個定時器定時消費佇列,最後也能用。哈哈
- 另外歡迎收藏我的資料網站:前端生活社群:
https://qianduan.life
- 感覺對你有幫助,可以
右下角
點個在看
,關注一波公眾號:[前端巔峰]