想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!前端
这是 Web 性能优化的第四篇,以前的能够在下面点击查看:react
React.js 核心团队一直在努力使 React 变得更快,就像燃烧的速度同样。为了让开发者可以加速他们的 React 应用程序,为此增长了不少工具:git
在这篇文章中,咱们将介绍 React v16.6 中新增的另外一个优化技巧,以帮助加速咱们的函数组件:React.memo。github
提示:使用 Bit 共享和安装 React 组件。使用你的组件来构建新的应用程序,并与你的团队共享它们以更快地构建。segmentfault
组件构成 React 中的一个视图单元。这些组件具备状态,此状态是组件的本地状态,当状态值因用户操做而更改时,组件知道什么时候从新渲染。如今,React 组件能够从新渲染 五、10 到 90次。有时这些从新渲染多是必要的,但大多数状况下不是必需的,因此这些没必要要的这将致使咱们的应用程序严重减速,下降了性能。数组
来看看如下这个组件:浏览器
import React from 'react' class TestC extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate') } componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate') } render () { return ( <div> {this.state.count} <button onClick={ () => this.setState({count: 1})}>Click Me</button> </div> ) } } export default TestC;
该组件的初始状态为 {count: 0}
。当咱们单击 click Me 按钮时,它将 count
状态设置为 1。屏幕的 0
就变成了 1
。.当咱们再次单击该按钮时出现了问题,组件不该该从新呈现,由于状态没有更改。count
的上个值为1,新值也 1,所以不须要更新 DOM。缓存
这里添加了两个生命周期方法来检测当咱们两次设置相同的状态时组件 TestC 是否会更新。我添加了componentWillUpdate,当一个组件因为状态变化而肯定要更新/从新渲染时,React 会调用这个方法;还添加了componentdidUpdate,当一个组件成功从新渲染时,React 会调用这个方法。性能优化
在浏览器中运行咱们的程序,并屡次单击 Click Me
按钮,会看到在控制打印不少次信息:函数
在咱们的控制台中有 “componentWillUpdate” 和 “componentWillUpdate” 日志,这代表即便状态相同,咱们的组件也在从新呈现,这称为浪费渲染。
为了不 React 组件中的渲染浪费,咱们将挂钩到 shouldComponentUpdate
生命周期方法。
shouldComponentUpdate 方法是一个生命周期方法,当 React 渲染 一个组件时,这个方法不会被调用 ,并根据返回值来判断是否要继续渲染组件。
假若有如下的 shouldComponentUpdadte
:
shouldComponentUpdate (nextProps, nextState) { return true }
props
值。state
值。在上面,告诉 React 要渲染咱们的组件,这是由于它返回 true
。
若是咱们这样写:
shouldComponentUpdate(nextProps, nextState) { return false }
咱们告诉 React 永远不要渲染组件,这是由于它返回 false
。
所以,不管什么时候想要渲染组件,都必须返回 true
。如今,能够重写 TestC 组件:
import React from 'react'; class TestC extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate') } componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate') } shouldComponentUpdate(nextProps, nextState) { if (this.state.count === nextState.count) { return false } return true } render() { return ( <div> { this.state.count } <button onClick = { () => this.setState({ count: 1 }) }> Click Me </button> </div> ); } } export default TestC;
咱们在 TestC 组件中添加了 shouldComponentUpdate
,咱们检查了当前状态对象this.state.count
中的计数值是否等于 ===
到下一个状态 nextState.count
对象的计数值。 若是它们相等,则不该该从新渲染,所以咱们返回 false
,若是它们不相等则返回 true
,所以应该从新渲染以显示新值。
在咱们的浏览器中测试,咱们看到咱们的初始渲染:
若是咱们屡次点击 click Me
按钮,咱们只会获得:
componentWillUpdate componentDidUpdate
咱们能够从 React DevTools 选项卡中操做 TestC 组件的状态,单击 React 选项,选择右侧的 TestC,咱们将看到带有值的计数状态:
在这里,咱们能够改变数值,点击count
文本,输入 2
,而后回车:
你会看到状态计数增长到 2
,在控制台会看到:
componentWillUpdate componentDidUpdate componentWillUpdate componentDidUpdate
这是由于上个值为 1
且新值为 2
,所以须要从新渲染。
如今,使用 纯组件。
React在v15.5中引入了Pure Components。 这启用了默认的相等性检查(更改检测)。 咱们没必要将 shouldComponentUpdate
生命周期方法添加到咱们的组件以进行更改检测,咱们只须要继承 React.PureComponent
,React 将会本身判断是否须要从新渲染。
1)继承 React.PureComponent 时,不能再重写 shouldComponentUpdate
,不然会引起警告
Warning: ListOfWords has a method called shouldComponentUpdate(). shouldComponentUpdate should not be used when extending React.PureComponent. Please extend React.Component if shouldComponentUpdate is used.
2)继承PureComponent时,进行的是浅比较,也就是说,若是是引用类型的数据,只会比较是否是同一个地址,而不会比较具体这个地址存的数据是否彻底一致。
class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; } } class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { // This section is bad style and causes a bug const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return ( <div> <button onClick={this.handleClick}>click</button> <ListOfWords words={this.state.words} /> </div> ); } }
上面代码中,不管你怎么点击按钮,ListOfWords
渲染的结果始终没变化,缘由就是WordAdder的 word
的引用地址始终是同一个。
固然若是想让你变化,只要在 WordAdder 的 handleClick
内部,将 const words = this.state.words;
改成 const words = this.state.words.slice(0)
,就好了,由于改变了引用地址。
3)浅比较会忽略属性或状态突变的状况,其实也就是,数据引用指针没变而数据被改变的时候,也不新渲染组件。但其实很大程度上,咱们是但愿从新渲染的。因此,这就须要开发者本身保证避免数据突变。
接着让咱们修改咱们的 TestC 组件来使用 PureComponent:
import React from 'react'; class TestC extends React.PureComponent { constructor(props) { super(props); this.state = { count: 0 } } componentWillUpdate(nextProps, nextState) { console.log('componentWillUpdate') } componentDidUpdate(prevProps, prevState) { console.log('componentDidUpdate') } /*shouldComponentUpdate(nextProps, nextState) { if (this.state.count === nextState.count) { return false } return true }*/ render() { return ( <div> { this.state.count } <button onClick = { () => this.setState({ count: 1 }) }> Click Me </button> </div > ); } } export default TestC;
这里注释掉了 shouldComponentUpdate
实现,咱们不须要它,由于 React.PureComponent
将为咱们作检查。
试它,从新加载你的浏览器,并点击屡次点击 Click Me
按钮:
如今,咱们已经看到如何在 React 中优化类组件中的从新渲染,让咱们看看咱们如何在函数组件中实现一样的效果。
如今,咱们看到了如何使用 Pure Components 和 shouldComponentUpdate
生命周期方法优化上面的类组件,是的,类组件是 React 的主要组成部分。 可是函数也能够在 React 中用做为组件。
function TestC(props) { return ( <div> I am a functional component </div> ) }
这里的问题是,函数组件没有像类组件有状态(尽管它们如今利用Hooks useState的出现使用状态),并且咱们不能控制函数组件的是否从新渲染,由于咱们不能像在类组件中使用生命周期方法。
若是能够将生命周期钩子添加到函数组件,那么就以添加 shouldComponentUpdate
方法来告诉React 何时从新渲染组件。 固然,在函数组件中,咱们不能使用 extend React.PureComponent
来优化咱们的代码
让咱们将 TestC 类组件转换为函数组件。
import Readct from 'react'; const TestC = (props) => { console.log(`Rendering TestC :` props) return ( <div> {props.count} </div> ) } export default TestC; // App.js <TextC count={5}/>
在第一次渲染时,它将打印 Rendering TestC :5
。
打开 DevTools 并单击 React 选项。在这里,更改 TestC
组件的 count
为 5
.
若是咱们更改数字并按回车,组件的 props
将更改成咱们在文本框中输入的值,接着继续更为 45
:
移动到 Console 选项
咱们看到 TestC 组件从新渲染,由于上个值为 5,当前值为 45.如今,返回 React 选项并将值更改成 45
,而后移至 Console:
看到组件从新渲染,且上个值与当前值是同样的。
咱们如何控制从新渲染?
React.memo(...)
是 React v16.6 中引入的新功能。 它与 React.PureComponent
相似,它有助于控制 函数组件 的从新渲染。 React.memo(...)
对应的是函数组件,React.PureComponent
对应的是类组件。
这很简单,假设有一个函数组件,以下:
const Funcomponent = ()=> { return ( <div> Hiya!! I am a Funtional component </div> ) }
咱们只需将 FuncComponent
做为参数传递给 React.memo
函数:
const Funcomponent = ()=> { return ( <div> Hiya!! I am a Funtional component </div> ) } const MemodFuncComponent = React.memo(Funcomponent)
React.memo
会返回了一个纯组件 MemodFuncComponent
。 咱们将在 JSX 标记中渲染此组件。 每当组件中的 props
和 state
发生变化时,React 将检查 上一个 state
和 props
以及下一个 props
和 state
是否相等,若是不相等则函数组件将从新渲染,若是它们相等则函数组件将不会从新渲染。
如今来试试 TestC 函数组件:
let TestC = (props) => { console.log('Rendering TestC :', props) return ( <div> { props.count } </> ) } TestC = React.memo(TestC);
打开浏览器并加载应用程序,打开 DevTools 并单击 React 选项,选择 <Memo(TestC)>
。
如今,若是咱们在右边编辑 count 值为到 89,会看到咱们的应用程序从新渲染:
若是咱们在将值改成与上个同样的值: 89:
不会有从新渲染!!
总结几个要点:
React.PureComponent
是银React.memo(…)
是金。React.PureComponent
是 ES6 类的组件React.memo(...)
是函数组件React.PureComponent
优化 ES6 类组件中的从新渲染React.memo(...)
优化函数组件中的从新渲染原文:
https://blog.bitsrc.io/improv...
你的点赞是我持续分享好东西的动力,欢迎点赞!
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq44924588...
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。