在React内部,React使用了几种比较聪明的技术来实现最小化更新UI所需的昂贵的DOM操做
的数量。javascript
对于许多应用来讲,使用React将很快速的渲染出用户界面,从而无需进行大量工做来专门作优化性能的工做。java
大概有如下有几种方法来加快你的React应用程序。react
若是你在React应用中进行基准测试或这遇到了性能问题,请首先确保你是使用的压缩后线上版本js文件来进行的测试:webpack
对于Create React App
来讲,你须要在构建时运行npm run build
。web
对于单文件来讲,咱们提供了生产环境版本.min.js
。npm
使用的是Browserify,你先设置NODE_ENV=production
而后运行。数组
使用的是webpack,你须要在生产环境配置中加入如下插件:性能优化
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), new webpack.optimize.UglifyJSPlugin();
在构建应用程序时开发构建工具能够打印一些有帮助的额外警告。
可是因为须要额外地记录这些警告信息,因此它也会变得更慢。数据结构
React会建立并维护所渲染的UI内部表示信息。其中包括从组件返回的React元素。 此表示信息使React避免建立DOM节点和访问那些没有必要的节点,由于这样作可能会比JavaScript对象上的一些操做更慢。 有时它被称为“虚拟DOM”
。dom
当组件的props
或state
更改时,React经过将最新返回的元素与先前渲染的元素进行比较来决定是否须要实际的DOM更新。 当它们不相等时,React将更新DOM。
在某些状况下,您的组件能够经过重写生命周期函数shouldComponentUpdate
来加快全部这些操做。这个函数会在从新渲染以前触发。 此函数的默认实现返回true
,让React执行更新:
shouldComponentUpdate(nextProps, nextState) { return true; }
若是你知道在某些状况下你的组件不须要更新,你能够从shouldComponentUpdate
中返回false
,而不是跳过整个渲染过程,其中包括调用当前组件和下面的render()
。
这里是一个组件的子树。 对于其中每个子树来讲,SCU
指示shouldComponentUpdate
返回什么,vDOMEq
指示渲染的React元素是否相等。 最后,圆圈的颜色表示组件是否必须从新处理。
由于shouldComponentUpdate
对于以C2
为根的子树返回了false
,因此React没有尝试渲染C2
,所以甚至没必要在C4
和C5
上调用shouldComponentUpdate
。
对于C1
和C3
,shouldComponentUpdate
返回true,所以React必须下到子树中并检查它们。 对于C6
子树shouldComponentUpdate
返回true
,而且由于渲染的元素不是相同的,React不得不更新DOM。
最后一个有趣的例子是C8
。 React不得不渲染这个组件,不过因为React元素返回的元素等于以前渲染的元素,因此它没必要更新DOM。
注意,React只须要作C6
的DOM从新处理,这是不可避免的。
对于C8
,它经过比较渲染的React元素来决定是否从新处理DOM。至于C2
的子树和C7
,咱们在shouldComponentUpdate
返回false
时它甚至都不须要比较元素,而且也没有调用render()
。
若是你的组件的惟一的改变方式就是改变props.color
或state.count
,你能够用shouldComponentUpdate
检查:
import React from 'react'; import ReactDOM from 'react-dom'; class CounterButton extends React.Component { constructor(props) { super(props); this.state = {count: 1}; this.click = this.click.bind(this); } click() { this.setState(prevState => ({ count: prevState.count + 1 })); } shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } return this.state.count !== nextState.count; } render() { return ( <button color={this.props.color} onClick={this.click}> Count:{this.state.count} </button> ); } } ReactDOM.render( <CounterButton color="blue"/>, document.getElementById('root') );
在这段代码中,shouldComponentUpdate
只是检查props.color
或state.count
是否有任何变化。 若是它们的值没有更改,则组件不更新。 若是你的组件比这个例子中的组件更复杂,你可使用相似的模式在props和state的全部字段之间作一个“浅比较”
,以肯定组件是否应该更新。
比较常见的模式是使用React提供的一个帮助对象来使用这个逻辑,能够直接继承React.PureComponent
。
因此上面这段代码有一个更简单的方法来实现一样的事情:
import React from 'react'; import ReactDOM from 'react-dom'; class CounterButton extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; this.click = this.click.bind(this); } click() { this.setState(prevState => ({ count: prevState.count + 1 })); } render() { return ( <button color={this.props.color} onClick={this.click}> Count: {this.state.count} </button> ); } } ReactDOM.render( <CounterButton color="blue"/>, document.getElementById('root') );
大多数时候,你可使用React.PureComponent
而不是编写本身的shouldComponentUpdate
。 它只作一个浅层的比较,因此你不须要直接使用它,若是你的组件内部props
或state
的数据有可能会忽然变化
,那么浅比较
将失效。
浅比较
的失效多是一个更加复杂的数据结构问题(忽然变化
)。 例如,假设您想要一个以逗号分隔单词列表的ListOfWords
组件,使用一个父WordAdder
组件,当你单击一个按钮用来添加一个单词到列表中时。 下面的代码将没法正常工做:
// PureComponent在内部会帮咱们对props和state进行简单对比(浅比较) // 值类型比较值,引用类型比较引用,可是不会比较引用类型的内部数据是否改变。 // 因此就会出现一个bug,无论你怎么点button,div是不会增长的。 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: ['zhangyatao']}; this.click = this.click.bind(this); } click() { // 这么写是不对的,由于state的更新是异步的,因此可能会致使一些没必要要的bug const words = this.state.word; words.push('zhangyatao'); this.setState({words: words}); } render() { return ( <div> <button onClick={this.click} /> <ListOfWords words={this.state.words} /> </div> ); } }
问题是PureComponent
将对this.props.words
的旧值和新值进行简单比较。 因为这个代码在WordAdder
的click
方法中改变了单词数组,因此即便数组中的实际单词已经改变,ListOfWords
组件中的this.props.words
的旧值和新值仍是相等的。 所以即使ListOfWords
具备要被渲染出来的新单词它也仍是不更新任何内容。
避免此问题的最简单的方法就是避免将那些可能忽然变化的数据
做为你的props或state。 例如,上面的click
方法里面使用concat
代替push
:
click() { this.setState(prevState => ({ count: prevState.words.concat(['zhangyatao']) })); }
ES6支持数组的spread
语法可让这变得更容易。 若是您使用的是Create React App
,那么此语法默承认以使用的。
click() { this.setState(prevState => ({ words: [...prevState.words, 'zhangyatao'] })); }
您还能够把那部分有可能忽然变化的数据
的代码按照上面的方式给重写下,从而以免这种问题。
例如,假设咱们有一个名为colormap
的对象,咱们要写一个函数,将colormap.right
改成'blue'
。 咱们能够写:
function updateColorMap(colormap) { colormap.right = 'blue'; }
要将上面的代码写成不会濡染改变的对象,咱们可使用Object.assign
方法:
function updateColorMap(colormap) { return Object.assign(colormap, {right: 'blue'}); }
updateColorMap
如今会返回一个新对象,而不是改变以前的旧对象。 Object.assign
在ES6中,须要polyfill
。
有一个JavaScript提议来添加对象spread
属性,以便不会忽然变化
的更新对象:
function updateColorMap(colormap) { return {...colormap, right: 'blue'}; }
若是使用Create React App
,默认状况下Object.assign
和对象spread
语法均可用。
Immutable.js
是另外一种解决这个问题的方法。 它提供不可变的,持久的集合,经过结构共享工做:
不可变:一旦建立,集合不能在另外一个时间点更改。
持久性:能够从先前的集合和类集合的突变中建立处一个新集合。 建立新集合后,原始集合仍然有效。
结构共享:使用尽量多的与原始集合相同的结构建立新集合,从而将最低程度的减小复制来提升性能。