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

寫本文的緣由

  • 有幾位小夥伴最近又來問這個問題,以前幫人解答過一次,今天寫下來
  • 之後有時間會多寫一些解決方案,例如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-dynamicreact

  • 這樣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都會致使瀏覽器渲染,達到一個閥值,就會觸發渲染,固然你也能夠手動控制清空隊列(這裏不寫太深,有興趣的能夠關注後面的文章)git

github

寫在最後

  • 我是Peter,架構設計過20萬人端到端加密超級羣功能的桌面IM軟件,如今是一名前端架構師。
  • 若是你對性能優化有很深的研究,能夠跟我一塊兒交流交流,今天這裏寫得比較淺,可是大部分人都夠用,以前問個人朋友,我讓它寫了一個定時器定時消費隊列,最後也能用。哈哈
  • 另外歡迎收藏個人資料網站:前端生活社區:https://qianduan.life
  • 感受對你有幫助,能夠右下角點個在看,關注一波公衆號:[前端巔峯]
相關文章
相關標籤/搜索