内容简介:在React内部,存在着初始化渲染和更新渲染的概念。初始化渲染会在组件第一次挂载时,渲染所有的节点当我们希望改变某个子节点时
在React内部,存在着初始化渲染和更新渲染的概念。
初始化渲染会在组件第一次挂载时,渲染所有的节点
当我们希望改变某个子节点时
我们所期望React帮我们实现的渲染行为是这样的
我们希望当我们的props向下传递时,只有对应需要更新的节点进行更新并重新渲染,其余节点不需要更新和重新渲染。
但是事实上,在默认的情况下,结果却是这样的
所有的组件树都被重新渲染,因为对于React而言,只要有props或者state发生了改变,我的组件就要重新渲染,所以除了绿色的节点,所有的黄色节点也被渲染了。
例子:
const Foo = ({foo}) => { console.log('Foo is rendering!'); return (<div>Foo {foo}</div>); } const Bar = ({bar}) => { console.log('Bar is rendering!'); return (<div>Bar {bar}</div>); } const FooBarGroup = ({foo, bar}) => { console.log('FooBar is rendering!'); return ( <div> <Foo foo={foo} /> <Bar bar={bar} /> </div> ) } class App extends React.Component { constructor(props) { super(props) this.state = { foo: 0, bar: 0 }; this.handleFooClick = this.handleFooClick.bind(this); this.handleBarClick = this.handleBarClick.bind(this); } handleFooClick (e) { e.preventDefault(); const newFoo = this.state.foo + 1; this.setState({foo: newFoo}); } handleBarClick(e) { e.preventDefault(); const newBar = this.state.bar + 1; this.setState({bar: newBar}); } render() { const {foo, bar} = this.state; return ( <div className="App"> <button onClick={this.handleFooClick}>Foo</button> <button onClick={this.handleBarClick}>Bar</button> <FooBarGroup foo={foo} bar={bar} /> </div> ); } } 复制代码
当我们点击Foo按钮的时候,因为只有传入Foo组件和FooBarGroup组件的foo这个props更新了,我们希望上只有Foo组件和FooBarGroup组件会被再次渲染。但是打开console你会发现,console中会出现Bar组件渲染时打印的log。证明Bar组件也被重新渲染了。
shouldComponentUpdate
工作原理
避免冗余渲染是一个常见的React性能优化方向。造成冗余渲染的原因是在默认情况下,shouldComponentUpdate()这个生命周期函数总是返回true( source code )意味着所有的组件在默认的情况下都会在组件树更新时去触发render方法。
React官方对于shouldComponentUpdate的工作与React组件树更新的机制有一个还不错的解释。React组件的更新决策可以分为两步,通过shouldComponetUpdate来确认是否需要重新渲染,React vDOM diff来确定是否需要进行DOM更新操作。 shouldComponentUpdate In Action
在上图中,C2节点不会重新触发render函数因为shouldComponentUpdate在C2节点返回了false,更新请求在此处被截断,相应的C2节点下的C4、C5节点也就不会触发render函数。
对于C3节点,由于shouldComponentUpdate返回了true,所以需要进行进一步的Vitural DOM的diff(以下简称vDOM diff,该diff算法由react提供,不在这细讲)。
而父组件的vDOM diff其实是对于子组件遍历进行以上过程。同上,C3的子组件C6由于shouldComponentUpdate返回了true,所以需要进行下一步vDOM diff,diff后发现需要更新,所以会重新触发渲染。而C7节点由于shouldComponentUpdate返回了false,所以便不再进行进一步的vDOM diff。而C8节点在vDOM diff后发现vDOM相等,最后其实也不会更新。
如何使用
上面提到了,默认情况下,shouldComponentUpdate这个方法总是会返回True。如果我们需要去显式的去决定我们的组件是否需要更新,那就意味着我们可以去显式调用这个生命周期函数。
class Foo extends React.Component { shouldComponentUpdate(nextProps) { return this.props.foo !== nextProps.foo; } render(){ console.log('Foo is rendering!'); return ( <div>{ this.props.foo }</div> ) } } const Bar = ({bar}) => { console.log('Bar is rendering!'); return (<div>{bar}</div>); } const FooBarGroup = ({foo, bar}) => { console.log('FooBarGroup is rendering!'); return ( <div> <Foo foo={foo} /> <Bar bar={bar} /> </div> ) } 复制代码
这时再去查看console,我们会发现只有当foo更新的时候,Foo组件才会真正的去调用render方法。
PureComponent
使用PureComponent
但是如果所有的组件我们都要去自己实现shouldComponentUpdate方法, 有的时候未免会显得很麻烦。不过好在React包里面提供了PureComponent这个实现。
PureComponent内部实现了一个基于props和state浅比较的shouldComponentUpdate方法,基于这种浅比较,当props和state没有发生改变时,组件的render方法便不会被调用到。
class Foo extends React.PureComponent { /* 我们不需要手动实现shouldCompoUpdate方法了 shouldComponentUpdate(nextProps) { return this.props.foo !== nextProps.foo; } */ render(){ console.log('Foo is rendering!'); return ( <div>{ this.props.foo }</div> ) } } 复制代码
PureComponent中的陷阱
我的props改变了,为什么我的组件没有更新?
由于PureComponent的shouldComponentUpdate是基于浅比较 shallowEqual.js 的,对于复杂对象,如果深层数据发生了改变,shouldComponentUpdate方法还是会返回false。
比如
class Foo extends PureComponent { constructor(props) { super(props); this.state = { foo: ['test'] } } handleClick = (e) => { e.preventDefault(); const foo = this.state.foo; foo.push('test'); //push是一个mutable的操作,foo的引用并没有改变 this.setState({foo}); } render(){ console.log('Foo is rendering!'); return ( <div> <button onClick={this.handleClick}>Foo balabala</button> { this.state.foo.length } </div> ) } } 复制代码
上面这段代码当我的button进行点击时,即使我的this.state.foo发生了改变,但是我的组件却不会有任何更新。因为我的this.state.foo( [‘test’]
)与nextState.foo( [‘test’, ‘test’]
)在shouldComponentUpdate进行的浅比较(实际使用 Object.is
)时其实是两个相同的两个数组。
解决办法
// concat会返回一个新数组,引用发生改变, 浅比较(Object.is)会认为这是两个不同的数组 handleClick = (e) => { e.preventDefault(); const foo = this.state.foo; const bar = foo.concat('test'); this.setState({foo: bar}); } 复制代码
我的props没有变, 为什么我的组件更新了?
然而有的时候, 即使我是PureComponent, 在组件的props看上去没有发生改变的时候, 组件还是被重新渲染了, interesting。
const Foo = ({foo}) => { console.log('Foo is rendering!'); return (<div>{foo}</div>); } const Bar = ({bar}) => { console.log('Bar is rendering!'); return (<div>{bar}</div>); } const FooBarGroup = ({foo, bar}) => { console.log('FooBar is rendering!'); return ( <div> <Foo foo={foo} /> <Bar bar={bar} /> </div> ) } class PureRenderer extends React.PureComponent { render(){ console.log('PureRenderer is rendering!!'); const { text } = this.props; return ( <div> {text} </div> ) } } class App extends React.Component { constructor(props) { super(props) this.state = { foo: 0, bar: 0 } } handleFooClick = (e) => { e.preventDefault(); const newFoo = this.state.foo + 1; this.setState({foo: newFoo}); } handleBarClick = (e) => { e.preventDefault(); const newBar = this.state.bar + 1; this.setState({bar: newBar}); } render() { const {foo, bar} = this.state; return ( <div className="App"> <button onClick={this.handleFooClick}>Foo</button> <button onClick={this.handleBarClick}>Bar</button> <FooBarGroup foo={foo} bar={bar} /> <PureRenderer text="blablabla" onClick={() => console.log('blablabla')} /> </div> ); } } 复制代码
我的PureRenderer明明已经是一个PureComponent了。但是当我点击foo或者bar button时,我还是能发现我的render方法被调到了。我似乎并没有进行任何props的修改?
导致这种情况是因为props。onClick传入的是 ()=>{console.log('balabalabla'
。这就意味着我每次传入的都是一个新的函数实体。对于两个不同的实例进行浅比较, 我总会得到这两个对象不相等的结果。(引用比较)
解决办法其实也很简单, 就是持久化下来我们的对象
const onClickHandler = () => console.log('blablabla') class App extends Component { constructor(props) { super(props) this.state = { foo: 0, bar: 0 }; } render() { const {foo, bar} = this.state; return ( <div className="App"> <button onClick={this.handleFooClick}>Foo</button> <button onClick={this.handleBarClick}>Bar</button> <FooBarGroup foo={foo} bar={bar} /> <DateRerender text="blablabla" onClick={onClickHandler}/> </div> ); } 复制代码
这样就能避免不必要的重复渲染了。
React 性能检测
1. 与Chrome集成的TimeLine工具
React在开发者模式下通过调用User Timing API可以很好的Chrome的火炬图进行结合来对组件的性能进行检测。
Profiling Components with the Chrome Performance Tab
可以看到所有的React相关的函数调用和操作都出现在了Timeline之中。
2. Why did you update?
还有一个有意思的库也可以帮助我们做到这些。
这个库会提供一个高阶组件, 将你的组件使用这个高阶组件包裹一下, 打开console你就能发现这个库对于你的组件的profiling。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 一个鲜为人知的高性能组件注册及实现组件排序技巧
- go语言高性能缓存组件ccache分析
- 高性能配置中心 DuiC 1.4.0 发布,升级组件
- 深度 | 从Go高性能日志库zap看如何实现高性能Go组件
- 微信自用高性能通用key-value组件MMKV已开源!
- 腾讯开源基于 mmap 的高性能 key-value 组件 MMKV
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
阿里巴巴正传:我们与马云的“一步之遥”
方兴东、刘伟 / 江苏凤凰文艺出版社 / 2015-1 / 45.00
十几年来,方兴东与马云每年一次,老友聚首,开怀畅谈,阿里上市前,作者再次与马云深度对话,阿里上市前的布局,深入探讨了一系列人们关心的话题。 本书忠实记录了阿里壮大、马云封圣的历史。作者通过细致梳理和盘点,对阿里巴巴的15年成长史进行了忠实回顾。从海博翻译社到淘宝网,从淘宝商城到天猫,从支付宝到阿里云计算,从拉来软银的第一笔投资到纽交所上市,作者对其中涉及到的人物、细节都有生动展现;对于马云、......一起来看看 《阿里巴巴正传:我们与马云的“一步之遥”》 这本书的介绍吧!