寫本文的原因
有幾位小夥伴最近又來問這個問題,之前幫人解答過一次,今天寫下來
以後有時間會多寫一些解決方案,例如oom了,不用esbuild怎麼解決之類的等..
正式開始
主題:股票交易APP(IM場景前端交互高頻更新卡頓)
一個正常的股票交易APP,是很複雜的,大都用原生寫,但是有的公司沒錢啊,只能做一套web app或者用RN這些寫,也有用Flutter的(這就是沒錢又要玩,那怎麼辦呢?那就玩
乞丐版
呀)
一個股票交易APP的界面長這樣
首先金融交易類產品是IM產品的一種,大都使用私有基於TCP長鏈接私有協議或者wss協議,這裏推薦兩篇我之前寫的文章,這樣你來看本文效果會比較好。
問題重現
用戶收藏了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都會導致瀏覽器渲染,達到一個閥值,就會觸發渲染,當然你也可以手動控制清空隊列(這裏不寫太深,有興趣的可以關注後面的文章)
❞
推薦閱讀
張一鳴:爲什麼 BAT 挖不走我們的人才?
微信號 可以改了 !!!真事 !!
再見!杭州!再見!阿里巴巴!
2020年北京,上海擺攤夜市分佈
牛!月入2w,95後送外賣的程序員,送餐途中改bug
5 種將死的編程語言
掃碼加我微信進羣,內推和技術交流,大佬們零距離