读后笔记,看看大公司的优化经验-《规模化场景下的 Twitter Lite 与高性能 React 渐进式 Web 应用》

一直在苦恼项目的优化指标的问题。。。啊啊啊,然后看到下面这篇文章瞬间觉得写得好好,好有启发啊,记下来一下~
参考阅读:规模化场景下的 Twitter Lite 与高性能 React 渐进式 Web 应用

1. 使用基于路由的代码拆分机制

Webpack 虽然极为强大,但却难于学习。我们也曾经遭遇到 CommonsChunkPlugin 问题,且很难弄清其与我们部分循环代码依赖性的对接方式。考虑到这一点,我们最终只保留了 3 个 JavaScript 资源文件,且总计略大于 1 MB(gzip 传输格式则为 420 KB)。

在站点运行过程中,加载数个甚至单一大型 JavaScript 文件都可能给移动用户的网站浏览与交互带来巨大性能瓶颈。除了各大型脚本在传输过程中需要消耗更多网络资源及传输时长之外,浏览器的解析工作量也将因此有所提升。

从打包层面的代码优化方向:正确的代码分割策略。Twitter的文章里说他们的js资源总计略大于1M,压缩后大概420K…小的有点惊人了尊嘟假嘟,不过确实给了我一些启发:

  1. 是否可以得知一个用户的平均网速数据,来确定我们拆分包的一般大小应该为多少K?因为自从有了import动态引入进行代码分割的方式之后,我们的项目里就越来越多的无脑分割方式,我不确定这样各种组件全部拆分拆分、会比下载一个单一的js包速度更快吗?或者可以参考一下一个文件最大420/3≈150K左右的大小为宜。
  2. 可能需要更加关注一个正确的代码拆分策略。

实践能得到的性能提升数据可以通过lighthouse去测量,从下方的包下载进度条可以看到。
在这里插入图片描述

2. 避免使用可能造成跳帧的函数

跳帧问题主要在于在某一帧内由于某种JS代码的阻塞导致该帧没有进行页面渲染,出现跳帧现象,在用户看来就是卡顿。

目前大多数设备会每秒对屏幕显示内容进行 60 次刷新。如果其中运行有动画或者过渡效果,抑或用户进行页面滚动操作,则浏览器需要匹配设备的刷新率并提供一张新的图像——或者称为帧——以作为每次屏幕刷新的显示内容。
 
其中每一帧的持续时间约为略高于 16 毫秒(即 1 秒的六十分之一,约为 16.66 毫秒)。不过在实际场景中,浏览器仍有其它管理任务需要处理,因此整个刷新内容的生成时间约在 10 毫秒左右。如果无法满足这一条件,则帧显示速率将有所下降,导致屏幕上的内容出现跳动。这种现象通常被称为跳帧,且会给用户的体验造成负面影响。— Paul Lewis 著于《渲染性能》

3. 缩小图片尺寸

这个大家都比较清晰了,但是Twi文章里提供了一种性能观测方法:TimeLine的raster渲染线程来观测图片的解析到底占了多少帧,如果不想出现跳帧现象,(比如快速滑动长列表),图片的解析时间最好在1-2帧左右。
在这里插入图片描述

4. 在挂载及卸载大量组件时推迟渲染

react在挂载、卸载大量组件时占用了很多的计算资源,这时候可能会出现交互事件被阻塞的情况,点击了一个按钮无反应,我们应该考虑延后资源的渲染,把它放在浏览器有空的间隙中执行,是的,就是使用window.requestAnimationFrame.
问题情况大概就像这样:
在这里插入图片描述

解决方法的组件,写一个空闲时加载的高阶组件,这个和React-lazyload又有不同,我觉得在首屏显示,lazyload没有意义的组件中使用这个高阶组件应该是一个比较好的选择。


import hoistStatics from 'hoist-non-react-statics';
import React from 'react';

/**
* Allows two animation frames to complete to allow other components to update
* and re-render before mounting and rendering an expensive `WrappedComponent`.
*/
export default function deferComponentRender(WrappedComponent) {
 class DeferredRenderWrapper extends React.Component {
   constructor(props, context) {
     super(props, context);
     this.state = { shouldRender: false };
   }

   componentDidMount() {
     window.requestAnimationFrame(() => {
       window.requestAnimationFrame(() => this.setState({ shouldRender: true }));
     });
   }

   render() {
     return this.state.shouldRender ? <WrappedComponent {...this.props} /> : null;
   }
 }

 return hoistStatics(DeferredRenderWrapper, WrappedComponent);
}

优化数据源管理库的存储(Redux / Mobx)

1. 避免频繁进行状态存储

将频繁变更的数据放在组件内部进行管理,比放在Mobx内存储速度大概能快上50%。
可以看下面摘录的原文和图片,还没优化之前的图片输入时下面很多dispatch,这些都是存放到数据源Redux导致的额外开销。

尽管组件控制往往被作为理想的实践方案,但事实证明控制输入内容会导致每一次按键皆造成更新与重新渲染。
 
这一点在主频高达 3 GHz 的台式计算机上并不是问题,但对于 CPU 性能较为有限的小型移动设备而言,用户将在输入时遭遇明显的延迟——特别是在对输出内容中的大量字符进行删除时
 
为了保留当前所输入的推文值并计算剩余可输入字符数,我们使用一项受控组件并在每次按键时将输入内容中的当前值传递至我们的 Redux 状态内。
 
下图一款典型的 Android 5 设备,每次按键带来的变更都会导致约 200 毫秒的延迟。如果用户输入速度很快,则应用的实际运行状态将非常糟糕。事实上,用户经常报告称其字符插入点会到处乱窜并导致输入内容陷入混乱。
在这里插入图片描述
在这里插入图片描述

Service Workers

在使用Service Workers 的情况下,我们能够利用其推送通知并预缓存静态资源。

1. 预缓存资源

在发版更新中,serviceWorkers可以做到资源的预缓存,甚至是首次进入之前就预缓存资源,我们的http缓存都是用户的二次访问缓存,而serviceWorker的缓存仍然有其存在的意义。。

但是注意点在于:

在立即对Service Worker 进行注册时,其会阻碍全部其它网络请求。推迟Service Worker 注册允许我们对页面加载内容进行初始化,从而在并发请求上限之内完成必要的网络请求。
nbsp;
通过将ServiceWorker 注册推迟至其它API 请求、CSS 与图像资产加载完成之后,我们能够保证页面完成渲染并具备响应能力,具体如截图所示。

如大家所见,在未启用ServiceWorker 预缓存机制的情况下,当前视图中的每一项资产都需要从网络处加载并返回至应用程序处。在良好的3G 网络环境下,这一加载过程仍需要约6 秒方可结束。然而在启用ServiceWorker 的预缓存机制后(图22),同样的3G 网络可在1.5 秒以内完成页面加载——性能提升高达75%!
在这里插入图片描述

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值