详解使用React.memo来优化函数组件的性能_javascript技巧_脚本之家

日期:2020-05-07编辑作者:Web前端

时间: 2019-09-07阅读: 224标签: 优化

React核心开发团队一直都努力地让React变得更快。在React中可以用来优化组件性能的方法大概有以下几种:

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

组件懒加载和) Pure Component shouldComponentUpdate{...}生命周期函数

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

本文还会介绍React16.6加入的另外一个专门用来优化函数组件性能的方法: React.memo。

可以看出,性能优化商业上来说很重要。

无用的渲染

但是,更重要的还是屏幕前我们的用户,让用户在使用产品时有更快更舒适的浏览体验,这算是一种前端工程师的自我修养。

组件是构成React视图的一个基本单元。有些组件会有自己本地的状态, 当它们的值由于用户的操作而发生改变时,组件就会重新渲染。在一个React应用中,一个组件可能会被频繁地进行渲染。这些渲染虽然有一小部分是必须的,不过大多数都是无用的,它们的存在会大大降低我们应用的性能。

所以今天就分享一下如何去优化我们的 React 项目,进而提升用户体验。

import React from 'react';class TestC extends React.Component { constructor; this.state = { count: 0 } } componentWillUpdate { console.log('componentWillUpdate') } componentDidUpdate { console.log } render() { return (  {this.state.count} this.setState}>Click Me  ); }}export default TestC;

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

TestC组件有一个本地状态count,它的初始值是0。当我们点击Click Me按钮时,count的值被设置为1。这时候屏幕的数字将会由0变成1。当我们再次点击该按钮时,count的值还是1, 这时候TestC组件不应该被重新渲染,可是现实是这样的吗?

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

为了测试count重复设置相同的值组件会不会被重新渲染, 我为TestC组件添加了两个生命周期函数: componentWillUpdate和componentDidUpdate。componentWillUpdate方法在组件将要被重新渲染时被调用,而componentDidUpdate方法会在组件成功重渲染后被调用。

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

在浏览器中运行我们的代码,然后多次点击Click Me按钮,你可以看到以下输出:

如果我们写成这样,控制台会报错误:JSX parent expressions must have,告诉我们只能返回一个元素,所以我们通常会在最外层包裹一个 div 元素,如下所示:

我们可以看到'componentWillUpdate'和'componentWillUpdate'在每次我们点击完按钮后,都会在控制台输出来。所以即使count被设置相同的值,TestC组件还是会被重新渲染,这些就是所谓的无用渲染。

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

Pure Component/shouldComponentUpdate

这样做虽然能正常执行,但是会额外创建不必要的DOM节点,这可能会导致创建许多无用的元素,并且在我们的渲染数据来自特定顺序的子组件时,某些情况下也会生成许多无效的节点。请考虑以下代码:

为了避免React组件的无用渲染,我们可以实现自己的shouldComponentUpdate生命周期函数。

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

当React想要渲染一个组件的时候,它将会调用这个组件的shouldComponentUpdate函数, 这个函数会告诉它是不是真的要渲染这个组件。

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

如果我们的shouldComponentUpdate函数这样写:

table tr div tdcolumn one/td tdcolumn two/td /div /tr/table
shouldComponentUpdate { return true }

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

其中各个参数的含义是:

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

nextProps: 组件将会接收的下一个参数props nextProps: 组件的下一个状态state

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

因为我们的shouldComponentUpdate函数一直返回true,这就告诉React,无论何种情况都要重新渲染该组件。

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

shouldComponentUpdate { return false}

React.Lazy 帮助我们按需加载组件,从而减少我们应用程序的加载时间,因为只加载我们所需的组件。

因为这个方法的返回值是false,所以React永远都不会重新渲染我们的组件。

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

因此当你想要React重新渲染你的组件的时候,就在这个方法中返回true,否则返回false。现在让我们用shouldComponentUpdate重写之前的TestC组件:

class MyComponent extends Component{ render() { return (divMyComponent/div) }}const MyComponent = React.lazy(()=import('./MyComponent.js'))function App() { return (divMyComponent //div)}
import React from 'react';class TestC extends React.Component { constructor; this.state = { count: 0 } } componentWillUpdate { console.log('componentWillUpdate') } componentDidUpdate { console.log } shouldComponentUpdate { if (this.state.count === nextState.count) { return false } return true } render() { return (  { this.state.count }  this.setState }> Click Me   ); }}export default TestC;

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

我们在TestC组件里添加了shouldComponentUpdate方法,判断如果现在状态的count和下一个状态的count一样时,我们返回false,这样React将不会进行组件的重新渲染,反之,如果它们两个的值不一样,就返回true,这样组件将会重新进行渲染。

3使用React.Suspense

再次在浏览器中测试我们的组件,刚开始的界面是这样的:

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

这时候,就算我们多次点击Click Me按钮,也只能看到两行输出:

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

componentWillUpdatecomponentDidUpdate

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

因为第二次点击Click Me按钮后count值一直是1,这样shouldComponentUpdate一直返回false,所以组件就不再被重新渲染了。

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

那么如何验证后面state的值发生改变,组件还是会被重新渲染呢?我们可以在浏览器的React DevTools插件中直接对TestC组件的状态进行更改。具体做法是, 在Chrome调试工具中点击React标签,在界面左边选中TestC组件,在界面的右边就可以看到其状态state中只有一个键count,且其值是1:

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

然后让我们点击count的值1,将其修改为2,然后按回车键:

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

你将会看到控制台有以下输出:

5使用 shouldComponentUpdate() 防止不必要的重新渲染

componentWillUpdatecomponentDidUpdatecomponentWillUpdatecomponentDidUpdate

本文由www.129028.com金沙发布于Web前端,转载请注明出处:详解使用React.memo来优化函数组件的性能_javascript技巧_脚本之家

关键词:

【www.129028.com金沙】说说这碗青春饭

时间: 2019-09-08阅读: 311标签: 程序员 前不久,又听到有人说程序员这一行是碗青春饭,言语之中充满了鄙夷。当然他并...

详细>>

Js如何获取某一天所在的星期www.129028.com金沙?

时间: 2019-09-10阅读: 159标签: 日期 我们会遇到的需求的是,获取今天或者某一天所在星期的开始和结束日期。 我们这...

详细>>

www.129028.com金沙缓存一致性策略以及雪崩、穿透问题

为了表述方便,本文以数据库查询缓存为例,使用缓存可以减小对数据库的压力。 一、缓存原理 高并发情境下首先考...

详细>>

前端工程师如何才能不焦虑?

时间: 2019-09-09阅读: 99标签: 焦虑引言 时间: 2019-07-28阅读: 247标签: 技术 进入2019年,中国互联网充满了焦虑的气息,不...

详细>>