「框架篇」React 中 的 9 种优化技术

谷歌的数据代表,一个有 10 条数据 0.4 秒能够加载完的页面,在变成 30 条数据加载时间为 0.9 秒后,流量和广告收入减小了 20%。当谷歌地图的首页文件大小从 100kb 减小到 70~80kb 时,流量在第一周涨了 10%,接下来的三周涨了 25%。
javascript

腾讯的前端工程师根据长期的数据监控也发现页面的一秒钟延迟会形成 9.4% 的 PV 的降低,8.3% 跳出率的增长以及 3.5% 转化率的降低。前端

能够看出,性能优化商业上来讲很重要。java

可是,更重要的仍是屏幕前咱们的用户,让用户在使用产品时有更快更温馨的浏览体验,这算是一种前端工程师的自我修养。react

因此今天就分享一下如何去优化咱们的 React 项目,进而提高用户体验。express


1
使用React.Fragment 来避免向 DOM 添加额外的节点


咱们在写 React 代码时,会常常遇到返回一组元素的状况,代码像这样:数组


class Parent extends React.Component { render() { return ( <h1>Hello there!</h1> <h1>Hello there again!</h1> ) }}

若是咱们写成这样,控制台会报错误: JSX parent expressions must have one parent element   ,告诉咱们只能返回一个元素,因此咱们一般会在最外层包裹一个 div 元素,以下所示:

class Parent extends React.Component { render() { return (         <div>         <h1>Hello there!</h1> <h1>Hello there again!</h1>         </div> ) }}

这样作虽然能正常执行,可是会额外建立没必要要的 DOM 节点,这可能会致使建立许多无用的元素,而且在咱们的渲染数据来自特定顺序的子组件时,某些状况下也会生成许多无效的节点。请考虑如下代码:

class Table extends React.Component { render() { return ( <table> <tr> <Columns /> </tr> </table> ); }}
class Columns extends React.Component { render() { return ( <div> <td>column one</td> <td>column two</td> </div> ); }}

上面的代码将在咱们的组件中呈现如下内容:

<table> <tr> <div> <td>column one</td> <td>column two</td> </div> </tr></table>

这显然不是咱们想看到的,React 为咱们提供了 Fragments Fragments 容许咱们将子列表分组,而无需向 DOM 添加额外节点。咱们能够将组件从新编写为:

class Columns extends React.Component { render() { return ( <React.Fragment> <td>column one</td> <td>column two</td> </React.Fragment> ); }}

2
使用 React.Lazy 延迟加载组件

有时咱们只想在请求时加载部分组件,例如,仅在单击购物车图标时加载购物车数据,在用户滚动到该点时在长图像列表的底部加载图像等。浏览器


React.Lazy 帮助咱们按需加载组件,从而减小咱们应用程序的加载时间,由于只加载咱们所需的组件。缓存


React.lazy 接受一个函数,这个函数须要动态调用 import()。它必须返回一个 Promise,该 Promise 须要 resolve 一个 defalut export 的 React 组件。以下所示:性能优化


class MyComponent extends Component{ render() { return (<div>MyComponent</div>) }}const MyComponent = React.lazy(()=>import('./MyComponent.js'))function App() { return (<div><MyComponent /></div>)}

在编译时,使用 Webpack 解析到该语法时,它会自动地开始进行代码分割。最终,咱们的应用程序将会被分红含有多个 UI 片断的包,这些 UI 片断将在须要时加载,若是你使用 Create React App,该功能已配置好,你能马上使用 这个特性。 Next.js  也已支持该特性而无需再配置。

3
使用React.Suspense


在交换组件时,会出现一个小的时间延迟,例如在 MyComponent 组件渲染完成后,包含 OtherComponent 的模块尚未被加载完成,这可能就会出现白屏的状况,咱们可使用加载指示器为此组件作优雅降级,这里咱们使用 Suspense 组件来解决。微信


React.Suspense 用于包装延迟组件以在加载组件时显示后备内容。


// MyComponent.jsconst Mycomponent = React.lazy(()=>import('./component.js'))function App() { return ( <div> <Suspense fallback={<div>loading ..</div>}> <MyComponent /> </Suspense> </div>)}


上面的代码中,fallback 属性接受任何在组件加载过程当中你想展现的 React 元素。


你能够将 Suspense 组件置于懒加载组件之上的任何位置,你甚至能够用一个 Suspense 组件包裹多个懒加载组件。


const OtherComponent = React.lazy(() => import('./OtherComponent'));const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </div> );}

5
使用 shouldComponentUpdate() 防止没必要要的从新渲染


当一个组件的 propsstate 变动,React 会将最新返回的元素与以前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。


即便 React 只更新改变了的 DOM 节点,从新渲染仍然花费了一些时间。在大部分状况下它并非问题,可是若是渲染的组件很是多时,就会浮现性能上的问题,咱们能够经过覆盖生命周期方法 shouldComponentUpdate 来进行提速。


shouldComponentUpdate 方法会在从新渲染前被触发。其默认实现老是返回 true,若是组件不须要更新,能够在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及以后的操做。


shouldComponentUpdate(nextProps, nextState) {   return nextProps.next !== this.props.next  }

6
使用React.PureComponent 


React.PureComponent 与 React.Component 很类似。二者的区别在于 React.Component并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。


若是赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些状况下使用 React.PureComponent 可提升性能。


// 使用 React.PureComponentclass MyComponent extends React.PureComponent { render() { return (<div>MyComponent</div>) }}
class MyComponent extends React.Component { render() { return (<div>MyComponent</div>) }}

React.PureComponent 中的 shouldComponentUpdate() 仅做对象的浅层比较。若是对象中包含复杂的数据结构,则有可能由于没法检查深层的差异,产生错误的比对结果。仅在你的 props 和 state 较为简单时,才使用 React.PureComponent,或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。你也能够考虑使用 immutable 对象加速嵌套数据的比较。


7
使用 React.memo 来缓存组件

React.memo 使用了缓存,缓存技术用于经过存储昂贵的函数调用的结果来加速程序,并在再次发生相同的输入时返回缓存的结果。


若是你的函数组件在给定相同 props 的状况下渲染相同的结果,那么你能够经过将其包装在  React.memo  中调用,以此经过记忆组件渲染结果的方式来提升组件的性能表现。这意味着在这种状况下,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。

默认状况下其只会对复杂对象作浅层对比,若是你想要控制对比过程,那么请将自定义的比较函数经过第二个参数传入来实现。

const MyComponent = ({user}) =>{ const {name, occupation} = user; return ( <div> <h4>{name}</h4> <p>{occupation}</p> </div> )}// 比较函数function areEqual(prevProps, nextProps) { /* 若是把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 不然返回 false */}export default React.memo(MyComponent, areEqual);

8
使用 ComponentDidUnmount() 删除未使用的DOM 元素 


有些时候,存在一些未使用的代码会致使内存泄漏的问题,React 经过向咱们提供componentWillUnmount 方法来解决这个问题。


componentWillUnmount() 会在组件卸载及销毁以前直接调用。在此方法中执行必要的清理操做,例如,清除 定时器,取消网络请求或清除在 componentDidMount() 中建立的订阅等。


例如,咱们能够在组件销毁以前,清除一些事件处理程序:

componentWillUnmount() { document.removeEventListener("click", this.closeMenu);}


componentWillUnmount() 中不该调用 setState(),由于该组件将永远不会从新渲染。组件实例卸载后,将永远不会再挂载它。


9
其余优化技术

虚拟化长列表

若是你的应用渲染了长列表(上百甚至上千的数据),咱们推荐使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地下降从新渲染组件消耗的时间,以及建立 DOM 节点的数量。

react-window 和 react-virtualized 是热门的虚拟滚动库。它们提供了多种可复用的组件,用于展现列表、网格和表格数据。若是你想要一些针对你的应用作定制优化,你也能够建立你本身的虚拟滚动组件,就像 Twitter 所作的。


使用 Chrome Performance 标签分析组件


开发模式下,你能够经过支持的浏览器可视化地了解组件是如何 挂载、更新以及卸载的。例如:



在 Chrome 中进行以下操做:

  1. 临时禁用全部的 Chrome 扩展,尤为是 React 开发者工具。他们会严重干扰度量结果!
  2. 确保你是在 React 的开发模式下运行应用。
  3. 打开 Chrome 开发者工具的 Performance 标签并按下 Record
  4. 对你想分析的行为进行复现。尽可能在 20 秒内完成以免 Chrome 卡住。
  5. 中止记录。
  6. 在 User Timing 标签下会显示 React 归类好的事件。



最后,咱们探索了一些能够优化 React 应用程序的一些提升性能的方法,不局限于此。咱们应该根据须要有针对性的优化应用程序,由于在某些简单的场景中,过分的优化,可能会得不偿失。

🎀推荐阅读

  1. React.lazy() 和 Suspense 介绍及用法

  2. 使用 React.memo() 提高react应用性能



长按识别下方二维码,关注咱们吧(づ ̄3 ̄)❤~


来都来了,点个【好看】再走吧~~~


本文分享自微信公众号 - 像素摇摆(pxDance)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索