最近项目里出现两次前端计算量太大,导致页面交互卡顿的情况

案例一:图表切换

有两个图表,按钮切换显示。第二个图表的数据由第一个计算而来。因计算量太大,会导致切换图时,点击按钮后卡顿很久才渲染出。

1.避免重复计算数据

为了优化,我想到的第一个方法,保存计算后的数据,第一次后再切换就不用重新计算。
所以在父组件里用mobx@observable保存图表数据,把父组件保存的数据和修改数据的函数都传给图表组件。图表组件里是否有父组件传的数据,若没有则计算,计算后直接修改父组件的数据,这样之后的切换虽然重新mount图表,但数据可以直接给出。

2.计算数据的时间点

有两个选择:
1.父组件mount时,会导致第一张图加载变慢,对于一般只看第一个图的用户来说体验损失太大;
2.第二个图mount时,点击后卡顿严重,但不影响第一个图的加载;
于是我们将计算放到父组件mount完成之后异步计算数据。这样既不阻塞第一个图的加载,点击按钮切换也很顺滑。
但是!这里有个天大的问题,耗时的异步计算数据会阻塞当前页面的渲染(如页面滚动、图表的tooltip等)

  1. class Parent extends React.Component {
  2. @observable chartData = {}
  3. @action setChartData = (data) => { chartData = data }
  4. render () {
  5. return this.show
  6. ? <Chart1 />
  7. : <Chart2
  8. chartData={this.chartData}
  9. setChartData={this.setChartData}
  10. />
  11. }
  12. }
  13. class Chart2 extends React.Component {
  14. getData = () => {
  15. // 计算...
  16. this.props.setChartData(data)
  17. this.data = data
  18. // 是不是可以不用这个 this.data
  19. }
  20. componentDidMount() {
  21. this.getData()
  22. }
  23. render () {
  24. return (this.props.chartData || this.data)
  25. && <chart />
  26. }
  27. }

3.减少计算复杂度

最根本的解决方法,这就是学习算法的必要性。
背景是这样的,第一张图是数据图(有上万个数据),第二张图是第一张图的百分比图,如显示有90%的数据小于80这个值。
原来我的计算方案是,使用了某个库的percentile函数,调用一百次percentile函数,则复杂度为100×sort(10000)
后来发现不用每次调用percentile函数,自己排序一次,取percentile值即取排序数组的对应百分比位置的数即可。

  1. // 优化前
  2. const getPercentileChartData = (data, percentile) => {
  3. const result = [];
  4. for(let i = 0; i <= 100; i++) {
  5. result.push([percentile(i).toFixed(2), i]);
  6. }
  7. return result;
  8. };
  9. // 优化后
  10. const getPercentileChartData = (data) => {
  11. const sortedData = data.sort((a, b) => (a - b));
  12. const result = [];
  13. for(let i = 0; i <= 100; i++) {
  14. const percentileValue = i === 100
  15. ? sortedData[sortedData.length - 1]
  16. : sortedData[Math.floor((i * sortedData.length) / 100)];
  17. result.push([percentileValue, i]);
  18. }
  19. return result;
  20. };

案例二:大量数据下载

实现下载表格全部页数据的功能,但因为后端提供数据不全,需要前端处理后再下载,数据量大时下载会等待很久。

这里补充一点,怎么发现哪一步耗时过多, console.log大法好:-D

  1. 打console.log,找出耗时最长的;
  2. 打console.log,看是否有多余渲染;
  3. 不能打console.log的,注释某些步骤,看是否变快;

经过排查,发现原因在于,其中有一个字段要用一个方法生成链接,这个方法写的好烂,里面最耗时的异步是处理了大量store里的数据,每调用一次就处理一次,所以可以将处理后的数据保存起来,需要时引用即可。

案例三:Tab切换

Tab切换真是个前端经典场景,里面涉及问题很多:

  • 是否重新mount组件
  • 是否重新请求获取数据
  • 是否重新渲染组件
  • 是否保持组件原状态 ```javascript this.show ? :

this ```