前端性能:股票交易APP頻繁更新怎麼破

寫本文的原因

  • 有幾位小夥伴最近又來問這個問題,之前幫人解答過一次,今天寫下來

  • 以後有時間會多寫一些解決方案,例如oom了,不用esbuild怎麼解決之類的等..

正式開始

主題:股票交易APP(IM場景前端交互高頻更新卡頓)

  • 一個正常的股票交易APP,是很複雜的,大都用原生寫,但是有的公司沒錢啊,只能做一套web app或者用RN這些寫,也有用Flutter的(這就是沒錢又要玩,那怎麼辦呢?那就玩乞丐版呀)

一個股票交易APP的界面長這樣

問題重現

  • 用戶收藏了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 種將死的編程語言

掃碼加我微信進羣,內推和技術交流,大佬們零距離

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章